puanrs/
lib.rs

1//! # Puan rust
2//! 
3//! Puan algorithms implemented in Rust. 
4
5use std::{collections::HashMap};
6use std::fmt::Display;
7use itertools::Itertools;
8use polyopt::{GeLineq, Variable, Polyhedron, CsrPolyhedron};
9
10pub mod solver;
11pub mod linalg;
12pub mod polyopt;
13
14#[derive(Clone, Copy)]
15pub enum Sign {
16    Positive,
17    Negative
18}
19
20impl Sign {
21
22    /// Value representation of the various Sign choices.
23    /// `Sign::Positive` is represented by `1`. 
24    /// `Sign::Negative` is represented by `-1`. 
25    pub fn val(&self) -> i8 {
26        return match *self {
27            Sign::Positive => 1,
28            Sign::Negative => -1,
29        }
30    }
31}
32
33
34/// Data structure for representing an `at least` constraint on form 
35/// $$ c * v_0 + c * v_1 + ... + c * v_n + bias \ge 0 $$
36/// where $c=1$ if `sign == Sign::Positive`, or, $c=-1$ if `sign == Sign::Negative`.
37/// `ids` vector property holds what variables are connected to this constraint.
38pub struct AtLeast {
39    pub ids     : Vec<u32>,
40    pub bias    : i64,
41    pub sign    : Sign,
42}
43
44impl Clone for AtLeast {
45    fn clone(&self) -> Self {
46        return AtLeast {
47            bias: self.bias,
48            ids: self.ids.to_vec(),
49            sign: self.sign,
50        }
51    }
52}
53
54impl AtLeast {
55
56    fn size(&self) -> usize {
57        return self.ids.len();
58    }
59
60    /// Transform into a linear inequality constraint.
61    /// `variable_hm` is a hash map of id's and variables.
62    /// 
63    /// # Example:
64    /// 
65    /// ```
66    /// use puanrs::AtLeast;
67    /// use puanrs::Sign;
68    /// use puanrs::polyopt::GeLineq;
69    /// use puanrs::polyopt::Variable;
70    /// use std::{collections::HashMap};
71    /// let at_least: AtLeast = AtLeast {
72    ///     ids     : vec![0,1,2],
73    ///     bias    : -1,
74    ///     sign    : Sign::Positive,
75    /// };
76    /// let mut variable_hm: HashMap<u32, Variable> = HashMap::default();
77    /// variable_hm.insert(0, Variable {id: 0, bounds: (0,1)});
78    /// variable_hm.insert(1, Variable {id: 1, bounds: (0,1)});
79    /// variable_hm.insert(2, Variable {id: 2, bounds: (0,1)});
80    /// let actual: GeLineq = at_least.to_lineq(None, &variable_hm);
81    /// assert_eq!(actual.coeffs, vec![1,1,1]);
82    /// assert_eq!(actual.bias, -1);
83    /// assert_eq!(actual.bounds, vec![(0,1),(0,1),(0,1)]);
84    /// assert_eq!(actual.indices, vec![0,1,2]);
85    /// ```
86    pub fn to_lineq(&self, id: Option<u32>, variable_hm: &HashMap<u32, Variable>) -> GeLineq {
87        return GeLineq {
88            id: id,
89            coeffs: vec![self.sign.val() as i64; self.size()],
90            bias: self.bias,
91            bounds: self.ids.iter().map(
92                |id| {
93                    variable_hm.get(id).expect(
94                        &format!(
95                            "got id `{id}` in expression {self} that was not in theory"
96                        )
97                    ).bounds
98                }
99            ).collect(),
100            indices: self.ids.to_vec()
101        }
102    }
103
104    /// Transforms into a linear inequality constraint. Unlike `to_lineq` this is extended to include the given variable
105    /// such that it holds logic as "if `given_id` is true then `self` must be true".
106    fn to_lineq_extended(&self, given_id: u32, variable_hm: &HashMap<u32, Variable>) -> GeLineq {
107        return GeLineq::merge_disj(
108            &(variable_hm.get(&given_id).expect(&format!("variable id `{given_id}` does not exist in variable hash map"))).to_lineq_neg(),
109            &self.to_lineq(Some(given_id), variable_hm)
110        ).expect("could not merge disjunctions") // <- should always be able to merge here
111    }
112
113    /// Transforms into a linear inequality constraint. If sub constraints fulfills certain requirements, then the sub constraints
114    /// will be merged into this constraint. If the requirements are not met, then `to_lineq_extended` is called. 
115    fn to_lineq_reduced(&self, extend_default: bool, given_id: u32, variable_hm: &HashMap<u32, Variable>, statement_hm: &HashMap<u32, &Statement>) -> GeLineq {
116        return match (self.bias, self.ids.len()) {
117            (-1, 2) => {
118                let sub_lineqs: Vec<GeLineq> = self.ids.iter().filter_map(|id| {
119                    if let Some(statement) = statement_hm.get(id) {
120                        if let Some(expression) = statement.expression.clone() {
121                            return Some(expression.to_lineq(Some(given_id), variable_hm));
122                        }
123                    }
124                    return None;
125                }).collect();
126                if sub_lineqs.len() == 2 {
127                    if let Some(lineq) = GeLineq::merge_disj(&sub_lineqs[0], &sub_lineqs[1]) {
128                        return lineq;
129                    } else {
130                        return match extend_default {
131                            false => self.to_lineq(Some(given_id), variable_hm),
132                            true => self.to_lineq_extended(given_id, variable_hm),
133                        }
134                    }
135                } else {
136                    return match extend_default {
137                        false => self.to_lineq(Some(given_id), variable_hm),
138                        true => self.to_lineq_extended(given_id, variable_hm),
139                    }
140                }
141            },
142            (-2, 2) => {
143                let sub_lineqs: Vec<GeLineq> = self.ids.iter().filter_map(|id| {
144                    if let Some(statement) = statement_hm.get(id) {
145                        if let Some(expression) = statement.expression.clone() {
146                            return Some(expression.to_lineq(Some(given_id), variable_hm));
147                        }
148                    }
149                    return None;
150                }).collect();
151                if sub_lineqs.len() == 2 {
152                    if let Some(lineq) = GeLineq::merge_conj(&sub_lineqs[0], &sub_lineqs[1]) {
153                        return lineq;
154                    } else {
155                        return match extend_default {
156                            false => self.to_lineq(Some(given_id), variable_hm),
157                            true => self.to_lineq_extended(given_id, variable_hm),
158                        }
159                    }
160                } else {
161                    return match extend_default {
162                        false => self.to_lineq(Some(given_id), variable_hm),
163                        true => self.to_lineq_extended(given_id, variable_hm),
164                    }
165                }
166            },
167            _ => match extend_default {
168                false => self.to_lineq(Some(given_id), variable_hm),
169                true => self.to_lineq_extended(given_id, variable_hm),
170            }
171        }
172    }
173}
174
175impl Display for AtLeast {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        write!(f, "({})({}>={})", match self.bias < 0 {true => "+", false => "-"}, self.ids.iter().map(|v| v.to_string()).collect::<Vec<String>>().join(","), self.bias)
178    }
179}
180
181/// A `Statement` is a declaration of an expression (or proposition) connected to a `Variable`.
182/// For instance, "A is true iff x > 3, else false" is a statement. Currently only `AtLeast` is
183/// considered to be an `Expression`.
184pub struct Statement {
185    pub variable    : Variable,
186    pub expression  : Option<AtLeast>
187}
188
189impl Clone for Statement {
190    fn clone(&self) -> Self {
191        return Statement {
192            variable: self.variable.clone(),
193            expression: match &self.expression {
194                Some(exp) => Some(exp.clone()),
195                None => None
196            }
197        }
198    }
199}
200
201/// A `Theory` is a list of statements with a common name (id).
202pub struct Theory {
203    pub id : String,
204    pub statements : Vec<Statement>
205}
206
207impl Clone for Theory {
208    fn clone(&self) -> Self {
209        return Theory {
210            id: self.id.clone(),
211            statements: self.statements.to_vec()
212        }
213    }
214}
215
216impl Theory {
217
218    // top node/id, i.e the id which no one has as a child
219    fn _top(&self) -> &u32 {
220        let mut is_child : bool = true;
221        let mut id_curr : &u32 = &self.statements.first().expect("theory is empty").variable.id;
222        let mut counter = 0;
223        while is_child {
224            if counter >= self.statements.len() {
225                panic!("theory has circular statement references! (a -> b -> c -> a)")
226            }
227            for statement in self.statements.iter() {
228                is_child = match &statement.expression {
229                    Some(a) => a.ids.contains(id_curr),
230                    None => false
231                };
232                if is_child {
233                    id_curr = &statement.variable.id;
234                    break;
235                }
236            }
237            counter += 1;
238        }
239        return id_curr;
240    }
241
242    // All bottom / atomic variable ids, e.i. statements that don't have any children.
243    fn _bottoms(&self) -> Vec<&u32> {
244        return self.statements.iter().filter_map(
245            |statement| {
246                match &statement.expression {
247                    Some(_) => None,
248                    None => Some(&statement.variable.id)
249                }
250            }
251        ).collect()
252    }
253
254    // Variable hash map from variable id to variable
255    fn _variable_hm(&self) -> HashMap<u32, Variable> {
256        return self.statements.iter().map(|statement: &Statement| {
257            (statement.variable.id, statement.variable)
258        }).collect();
259    }
260
261    // Statement hash map from variable id to statement
262    fn _statement_hm(&self) -> HashMap<u32, &Statement> {
263        return self.statements.iter().map(|statement: &Statement| {
264            (statement.variable.id, statement)
265        }).collect();
266    }
267
268    /// Propagates all information bottoms-up in the Theory and compiles
269    /// into a HashMap from variable id to bounds.
270    ///
271    /// # Example:
272    /// 
273    /// ```
274    /// use puanrs::Theory;
275    /// use puanrs::Statement;
276    /// use puanrs::Sign;
277    /// use puanrs::AtLeast;
278    /// use puanrs::polyopt::Variable;
279    /// use std::collections::HashMap;
280    /// let theory: Theory = Theory {
281    ///    id          : String::from("A"),
282    ///    statements  : vec![
283    ///        Statement {
284    ///            variable    : Variable {
285    ///              id      : 0,
286    ///              bounds  : (0,1),
287    ///            },
288    ///            expression  : Some(
289    ///                AtLeast {
290    ///                    ids     : vec![1,2],
291    ///                    bias    : -1,
292    ///                    sign    : Sign::Positive
293    ///                }
294    ///            )
295    ///       },
296    ///       Statement {
297    ///           variable    : Variable {
298    ///               id      : 1,
299    ///               bounds  : (0,1),
300    ///           },
301    ///           expression  : None
302    ///       },
303    ///       Statement {
304    ///           variable    : Variable {
305    ///               id      : 2,
306    ///               bounds  : (1,1),
307    ///           },
308    ///           expression  : None
309    ///       },
310    ///    ]
311    /// };
312    /// let actual: HashMap<u32, (i64, i64)> = theory.propagate();
313    /// let expected: HashMap<u32, (i64, i64)> = vec![
314    ///     (0, (1,1)),
315    ///     (1, (0,1)),
316    ///     (2, (1,1)),
317    /// ].into_iter().collect();
318    /// assert_eq!(actual, expected);
319    /// ```
320    /// 
321    pub fn propagate(&self) -> HashMap<u32, (i64, i64)> {
322
323        // We add the primitive variables directly to the
324        // result since they are already propagated.
325        let mut variable_bounds: HashMap<u32, (i64, i64)> = self.statements
326            .iter()
327            .filter(|statement| statement.expression.is_none())
328            .map(|statement| (statement.variable.id, statement.variable.bounds))
329            .collect();
330        let statements_hm: HashMap<u32, &Statement> = self._statement_hm();
331
332        // We don't need to iterate over all primitive variables since
333        // they are already propagated.
334        let mut queue: Vec<u32> = self.statements
335            .iter()
336            .filter(|statement| statement.expression.is_some())
337            .map(|statement| statement.variable.id)
338            .collect();
339
340        // Popping from of queue until it is empty
341        while let Some(id) = queue.pop() {
342
343            if let Some(statement) = statements_hm.get(&id) {
344
345                if let Some(expression) = &statement.expression {
346
347                    // Check if all ids in expression is set in variable_bounds (has been propagated)
348                    if let Some(bounds) = expression.ids.iter().map(|id| variable_bounds.get(id)).collect::<Option<Vec<&(i64, i64)>>>() {
349    
350                        // Get bounds for expression
351                        let bounds_expression: (i64, i64) = bounds.iter().fold((0,0), |acc, bound| {
352                            match expression.sign {
353                                
354                                // This is simply adding all children bounds together if
355                                // the sign is positive, e.g. x + y + z -1 >= 0 should resolve into
356                                // [0,1]+[0,1]+[0,1] -1 >= 0 == [-1,2] >= 0 == [0,1]
357                                Sign::Positive => (acc.0 +bound.0, acc.1 + bound.1),
358
359                                // NOTE HERE that if the sign is negative we do not only
360                                // subtract the bound but we also flip the bounds, e.g.
361                                // -x -y -z +1 >= 0 should resolve into [-1,0]+[-1,0]+[-1,0]+1 >= 0 == [-2,1] >= 0 == [0,1]
362                                Sign::Negative => (acc.0 - bound.1, acc.1 - bound.0),
363                            }
364                        });
365
366                        // Update variable bounds
367                        variable_bounds.insert(
368                            statement.variable.id,
369                            (
370                                (bounds_expression.0 + expression.bias >= 0) as i64,
371                                (bounds_expression.1 + expression.bias >= 0) as i64, 
372                            )
373                        );
374    
375                    } else {
376
377                        // If we don't check that it actually exists in statements map
378                        // we know that we will never be able to propagate fully and we
379                        // will end up in an infinite loop.
380                        if !statements_hm.contains_key(&id) {
381                            panic!("statement id `{id}` does not exist in statements hash map");
382                        }
383
384                        // But if it does exists, we will eventually be able to propagate
385                        // it so we put it back in the queue.
386                        queue.push(id);
387                    }
388
389                } else {
390                    variable_bounds.insert(statement.variable.id, statement.variable.bounds);
391                }
392
393            }
394
395        }
396
397        return variable_bounds;
398    }
399
400    /// Transforms all Statements in Theory into GeLineq's such that 
401    /// if all GeLineq's are true <-> Theory is true.
402    ///
403    /// # Example:
404    /// 
405    /// ```
406    /// use puanrs::Theory;
407    /// use puanrs::Statement;
408    /// use puanrs::Sign;
409    /// use puanrs::AtLeast;
410    /// use puanrs::polyopt::Variable;
411    /// use puanrs::polyopt::GeLineq;
412    /// let theory: Theory = Theory {
413    ///     id          : String::from("A"),
414    ///     statements  : vec![
415    ///         Statement {
416    ///             variable    : Variable {
417    ///                 id      : 0,
418    ///                 bounds  : (0,1),
419    ///             },
420    ///             expression  : Some(
421    ///                 AtLeast {
422    ///                     ids     : vec![1,2],
423    ///                     bias    : -1,
424    ///                     sign    : Sign::Positive
425    ///                 }
426    ///             )
427    ///         },
428    ///         Statement {
429    ///             variable    : Variable {
430    ///                 id      : 1,
431    ///                 bounds  : (0,1),
432    ///             },
433    ///             expression  : None
434    ///         },
435    ///         Statement {
436    ///             variable    : Variable {
437    ///                 id      : 2,
438    ///                 bounds  : (0,1),
439    ///             },
440    ///             expression  : None
441    ///         },
442    ///     ]
443    /// };
444    /// let actual: Vec<GeLineq> = theory.to_lineqs(false, false);
445    /// assert_eq!(actual.len(), 1);
446    /// assert_eq!(actual[0].bias, 0);
447    /// assert_eq!(actual[0].coeffs, vec![-1,1,1]);
448    /// assert_eq!(actual[0].indices, vec![0,1,2]);
449    /// ```
450    pub fn to_lineqs(&self, active: bool, reduced: bool) -> Vec<GeLineq> {
451        let top_node_id = *self._top();
452        let var_hm: HashMap<u32, Variable> = self._variable_hm();
453        let state_hm: HashMap<u32, &Statement> = self._statement_hm();
454        let mut lineqs: Vec<GeLineq> = Vec::default();
455        let mut queue: Vec<u32> = vec![top_node_id];
456
457        while let Some(id) = queue.pop() {
458            if let Some(statement) = state_hm.get(&id) {
459                let pot_lineq = match &statement.expression {
460                    Some(a) => match reduced {
461                        true => Some(
462                            a.to_lineq_reduced(
463                                !((statement.variable.id == top_node_id) & active), 
464                                statement.variable.id,
465                                &var_hm,
466                                &state_hm,
467                            )
468                        ),
469                        false => match (statement.variable.id == top_node_id) & active {
470                            true => Some(a.to_lineq(Some(statement.variable.id), &var_hm)),
471                            false => Some(a.to_lineq_extended(statement.variable.id, &var_hm))
472                        }
473                    },
474                    None => None
475                };
476
477                if let Some(act_lineq) = pot_lineq {
478                    queue.append(&mut act_lineq.indices.clone().into_iter().filter(|i| (*i) != statement.variable.id).collect());
479                    lineqs.push(act_lineq);
480                }
481            }
482        }
483        return lineqs;
484    }
485
486    /// Converts Theory into a polyhedron. Set `active` param to true
487    /// to activate polyhedron, meaning assuming the top node to be true. 
488    /// Set `reduced` to true to potentially retrieve a reduced polyhedron.
489    pub fn to_ge_polyhedron(&self, active: bool, reduced: bool) -> CsrPolyhedron {
490        let lineqs = self.to_lineqs(active, reduced);
491        let mut index_bound_vec: Vec<(u32, (i64,i64))> = Vec::default();
492        for lineq in lineqs.iter() {
493            for (index, bound) in lineq.indices.iter().zip(lineq.bounds.iter()) {
494                let element = (*index, *bound);
495                if !index_bound_vec.contains(&element) {
496                    index_bound_vec.push(element);
497                }
498            }
499        }
500
501        let size = lineqs.iter().map(|l| l.indices.len()).sum(); 
502
503        let mut row: Vec<i64> = vec![0; size];
504        let mut col: Vec<i64> = vec![0; size];
505        let mut val: Vec<f64> = vec![0.0; size];
506        let mut i: usize = 0;
507        for (il, lineq) in lineqs.iter().enumerate() {
508            for (index, cf) in lineq.indices.iter().zip(lineq.coeffs.iter()) {
509                row[i] = il as i64;
510                col[i] = (*index) as i64;
511                val[i] = (*cf) as f64;
512                i += 1;
513            }
514        }
515        
516        let actual_indices: Vec<u32> = index_bound_vec.iter().map(|x| x.0).sorted().collect();
517        let index_bound_hm : HashMap<u32, (i64, i64)> = index_bound_vec.clone().into_iter().collect();
518        let variables: Vec<Variable> = actual_indices.iter().filter_map(|i| {
519            if let Some(bounds) = index_bound_hm.get(i) {
520                return Some(
521                    Variable {
522                        id: *i,
523                        bounds: bounds.clone()
524                    }
525                )
526            }
527            return None;
528        }).collect();
529        return CsrPolyhedron {
530            a: linalg::CsrMatrix { 
531                val: val, 
532                row: row,
533                col: col
534            },
535            b: lineqs.iter().map(|x| (-1*x.bias) as f64).collect(),
536            variables: variables.iter().map(|x| x.to_variable_float()).collect(),
537            index: lineqs.iter().map(|x| x.id).collect(),
538        };
539    }
540
541    /// Find solutions to this Theory. `objectives` is a vector of HashMap's pointing from
542    /// an id to a value. The solver will try to find a valid configuration that maximizes those values,
543    /// for each objective in objectives.
544    /// # Note
545    /// * Ids given in objectives which are not in Theory will be ignored.
546    /// * Ids which have not been given a value will be given 0 as default.
547    /// 
548    /// # Returns
549    /// A tuple of (solution: HashMap<u32, i64>, objective value: i64, status code: usize)
550    /// 
551    /// # Example:
552    /// 
553    /// ```
554    /// use puanrs::Theory;
555    /// use puanrs::Statement;
556    /// use puanrs::Sign;
557    /// use puanrs::AtLeast;
558    /// use puanrs::polyopt::Variable;
559    /// let t = Theory {
560    ///    id: String::from("A"),
561    ///     statements: vec![
562    ///         Statement {
563    ///            variable: Variable { id: 0, bounds: (0,1) },
564    ///            expression: Some(
565    ///                AtLeast {
566    ///                    ids: vec![1,2],
567    ///                    bias: -1,
568    ///                    sign: Sign::Positive
569    ///                }
570    ///            )
571    ///        },
572    ///        Statement {
573    ///            variable: Variable { id: 1, bounds: (0,1) },
574    ///            expression: Some(
575    ///                AtLeast {
576    ///                    ids: vec![3,4],
577    ///                    bias: 1,
578    ///                    sign: Sign::Negative
579    ///                }
580    ///            )
581    ///        },
582    ///        Statement {
583    ///            variable: Variable { id: 2, bounds: (0,1) },
584    ///            expression: Some(
585    ///                AtLeast {
586    ///                    ids: vec![5,6,7],
587    ///                    bias: -3,
588    ///                    sign: Sign::Positive,
589    ///                }
590    ///            )
591    ///        },
592    ///        Statement {
593    ///            variable: Variable { id: 3, bounds: (0,1) },
594    ///            expression: None
595    ///        },
596    ///        Statement {
597    ///            variable: Variable { id: 4, bounds: (0,1) },
598    ///            expression: None
599    ///        },
600    ///        Statement {
601    ///            variable: Variable { id: 5, bounds: (0,1) },
602    ///            expression: None
603    ///        },
604    ///        Statement {
605    ///            variable: Variable { id: 6, bounds: (0,1) },
606    ///            expression: None
607    ///        },
608    ///        Statement {
609    ///            variable: Variable { id: 7, bounds: (0,1) },
610    ///            expression: None
611    ///        },
612    ///     ]
613    /// };
614    /// let actual_solutions = t.solve(
615    ///     vec![
616    ///         vec![(3, 1.0), (4, 1.0)].iter().cloned().collect(),
617    ///     ],
618    ///     true
619    /// );
620    /// let expected_solutions = vec![
621    ///    vec![(3,1),(4,1),(5,1),(6,1),(7,1)].iter().cloned().collect(), // <- notice that these are from reduced columns (3,4,5,6,7)
622    /// ];
623    /// assert_eq!(actual_solutions[0].0, expected_solutions[0]);
624    pub fn solve(&self, objectives: Vec<HashMap<u32, f64>>, reduce_polyhedron: bool) -> Vec<(HashMap<u32, i64>, i64, usize)> {
625        let polyhedron: Polyhedron = self.to_ge_polyhedron(true,reduce_polyhedron).to_dense_polyhedron();
626        let _objectives: Vec<Vec<f64>> = objectives.iter().map(|x| {
627            let mut vector = vec![0.0; polyhedron.variables.len()];
628            for (k, v) in x.iter() {
629                let pot_index = polyhedron.variables.iter().position(|y| y.id == (*k));
630                if let Some(index) = pot_index {
631                    vector[index] = *v;
632                }
633            }
634            return vector;
635        }).collect();
636        return _objectives.iter().map(|objective| {
637            let ilp = solver::IntegerLinearProgram {
638                ge_ph: polyhedron.to_owned(),
639                eq_ph: Default::default(),
640                of: objective.to_vec(),
641            };
642            return ilp.solve();
643        }).map(|sol| {
644            (
645                polyhedron.variables.clone().iter().map(|x| x.id).zip(sol.x).collect(),
646                sol.z,
647                sol.status_code,
648            )
649        }).collect();
650    }
651
652    /// Given two positive integers `n` (width) and `m` (depth), this function creates a n-ary Theory/tree, with variable 
653    /// size $ n^0 + n^1 + ... + n^m $. Each Statement with Some expression has bias -1 and `n` number of children.
654    /// For eaxmple, `Theory::instance(2, 3, String::from("my-id"))` would create a binary tree/Theory of variable size $ 2^3 = 8 $.
655    /// # Note
656    /// This function was mainly created to support benchmarking tests for different Theory sizes.
657    pub fn instance(width: u32, depth: u32, id: String) -> Theory {
658        
659        let n: u32 = (0..depth).into_iter().map(|i| width.pow(i as u32)).sum();
660        let n_before_leafs: u32 = n - width.pow(depth-1);
661        return Theory {
662            id,
663            statements: (0..n).into_iter().map(|i| {
664                let start_index = i*width+1;
665                match i < n_before_leafs {
666                    true => Statement {
667                        variable: Variable {
668                            id: i,
669                            bounds: (0,1)
670                        },
671                        expression: Some(
672                            AtLeast {
673                                bias: -1,
674                                ids: (start_index..start_index+width).collect_vec(),
675                                sign: Sign::Positive,
676                            }
677                        )
678                    },
679                    false => Statement {
680                        variable: Variable {
681                            id: i,
682                            bounds: (0,1)
683                        },
684                        expression: None
685                    }
686                }
687            }).collect(), 
688        };
689    }
690
691}
692
693#[cfg(test)]
694mod tests {
695    use std::vec;
696
697    use crate::{linalg::Matrix, polyopt::VariableFloat};
698
699    use super::*;
700
701    use std::ops::Range;
702    use itertools::Itertools;
703
704    impl GeLineq {
705        
706        fn satisfied(&self, variables: Vec<(u32, i64)>) -> bool{
707            let mut res = 0;
708            for i in 0..variables.len() {
709                for j in 0..self.coeffs.len() {
710                    if self.indices[j] == variables[i].0 {
711                        res = res + self.coeffs[j]*variables[i].1;
712                    }
713                }
714            }
715            return res + self.bias >= 0;
716        }
717    }
718
719    #[test]
720    fn test_theory_propagate() {
721        let theory: Theory = Theory {
722            id          : String::from("A"),
723            statements  : vec![
724                Statement {
725                    variable    : Variable {
726                        id      : 0,
727                        bounds  : (0,1),
728                    },
729                    expression  : Some(
730                        AtLeast {
731                            ids     : vec![1,2],
732                            bias    : -1,
733                            sign    : Sign::Positive
734                        }
735                    )
736                },
737                Statement {
738                    variable    : Variable {
739                        id      : 1,
740                        bounds  : (0,1),
741                    },
742                    expression  : None
743                },
744                Statement {
745                    variable    : Variable {
746                        id      : 2,
747                        bounds  : (1,1),
748                    },
749                    expression  : None
750                },
751            ]
752        };
753        let actual: HashMap<u32, (i64, i64)> = theory.propagate();
754        let expected: HashMap<u32, (i64, i64)> = vec![
755            (0, (1,1)),
756            (1, (0,1)),
757            (2, (1,1)),
758        ].into_iter().collect();
759        assert_eq!(actual, expected);
760    }
761
762    #[test]
763    fn test_generate_instance_to_polyhedron() {
764        for (w,d) in [(2,2),(2,3),(2,4),(3,3),(3,4),(4,4)] {
765            let theory: Theory = Theory::instance(w, d, String::from("my-id"));
766            let polyhedron: Polyhedron = theory.to_ge_polyhedron(true, false).to_dense_polyhedron();
767            assert_eq!(polyhedron.variables.len(), theory.statements.len()-1);
768        }
769    }
770
771    // Validates that all combinations, generated from `variable_bounds`, in two vectors of GeLineqs gives the same result, i.e. they hold the same truth table```
772    // The `all_relation` states how the GeLineq within each vector relates. If `all_relation` is true then all GeLineqs of the vector must hold to produce `True` as output. If `all_relation` is false then it is treated as an any, and only one GeLineq of the vector must hold to produce `True` as output. 
773    fn validate_all_combinations(variable_bounds: Vec<(u32, (i64, i64))>, lineqs0: Vec<GeLineq>, lineqs1: Vec<GeLineq>, all_relation: bool) -> bool {
774
775        let base : i64 = 2;
776        let max_combinations: i64 = base.pow(15);
777        let bound_ranges: Vec<std::ops::Range<i64>> = variable_bounds.iter().map(|x| Range {start: x.1.0, end: x.1.1+1}).collect_vec();
778        let n_combinations : i64 = variable_bounds.iter().map(|x| (x.1.1+1)-x.1.0).product();
779        if n_combinations > max_combinations {
780            panic!("number of combinations ({n_combinations}) to test are more than allowed ({max_combinations})");
781        }
782
783        return bound_ranges.into_iter().multi_cartesian_product().all(|combination| {
784            let variable_ids : Vec<u32> = variable_bounds.iter().map(|x| x.0).collect_vec();
785            let variable_combination: Vec<(u32, i64)> = variable_ids.into_iter().zip(combination).collect();
786
787            let res0: bool;
788            let res1: bool;
789            if all_relation {
790                res0 = lineqs0.iter().all(|x| x.satisfied(variable_combination.clone()));
791                res1 = lineqs1.iter().all(|x| x.satisfied(variable_combination.clone()));
792            } else {
793                res0 = lineqs0.iter().any(|x| x.satisfied(variable_combination.clone()));
794                res1 = lineqs1.iter().any(|x| x.satisfied(variable_combination.clone()));
795            }
796            return (res0 & res1) | !(res0 & res1);
797        })
798    }
799    
800    #[test]
801    fn test_theory_to_lineqs_reduced() {
802
803        fn validate(t: Theory) -> bool {
804            let non_reduced: Vec<GeLineq> = t.to_lineqs(true, false);
805            let reduced: Vec<GeLineq> = t.to_lineqs(true, true);
806            let variable_bounds: Vec<(u32, (i64, i64))> = t.statements.into_iter().map(|x| (x.variable.id, x.variable.bounds)).collect();
807            return validate_all_combinations(variable_bounds, non_reduced, reduced, true)
808        }
809
810        // Depth 3
811        // 0: 1 & 2
812        // 1: 3 + 4 >= 6 (3 has bounds (-5,5), 4 is bool) 
813        // 2: 5 + 6 >= 4 (5 has bounds (-3,3), 6 is bool) 
814
815        // Expected constraints:
816        // 4( x) +( y) -41
817        let t = Theory {
818            id: String::from("A"),
819            statements: vec![
820                Statement {
821                    variable: Variable { id: 0, bounds: (0,1) },
822                    expression: Some(
823                        AtLeast {
824                            ids: vec![1,2],
825                            bias: -2,
826                            sign: Sign::Positive,
827                        }
828                    )
829                },
830                Statement {
831                    variable: Variable { id: 1, bounds: (0,1) },
832                    expression: Some(
833                        AtLeast {
834                            ids: vec![3,4],
835                            bias: -6,
836                            sign: Sign::Positive,
837                        }
838                    )
839                },
840                Statement {
841                    variable: Variable { id: 2, bounds: (0,1) },
842                    expression: Some(
843                        AtLeast {
844                            ids: vec![5,6],
845                            bias: -4,
846                            sign: Sign::Positive,
847                        }
848                    )
849                },
850                Statement {
851                    variable: Variable { id: 3, bounds: (-5,5) },
852                    expression: None
853                },
854                Statement {
855                    variable: Variable { id: 4, bounds: (0,1) },
856                    expression: None
857                },
858                Statement {
859                    variable: Variable { id: 5, bounds: (-3,3) },
860                    expression: None
861                },
862                Statement {
863                    variable: Variable { id: 6, bounds: (0,1) },
864                    expression: None
865                },
866            ]
867        };
868        assert!(validate(t));
869
870        // Depth 4
871        // 0: 1 & 2 & 3
872        // 1: 4 | 5
873        // 2: 5 | 6
874        // 3: 7 & 8
875        // 4: 9 & 10
876        // 5: -11
877        // 6: 12 | 13
878
879        // We test two things:
880        // 1) sharing variable works (both 1 and 2 has 5 as child and is reducable)
881        // 2) direct reducement works (0 and 3 has same constraint, therefore 0 should have 7 and 8 directly as children)  <- NOT IMPLEMENTED
882
883        // Expected constraints:
884        //   ( 1) +( 2) +( 3) -3
885        // -2( 3) +( 7) +( 8) +0
886        // -1(11) +(12) +(13) +0 (instead of 5 & 6)
887        //   ( 9) +(10)-2(11) +0 (instead of 4 & 5)
888
889        let t = Theory {
890            id: String::from("A"),
891            statements: vec![
892                Statement {
893                    variable: Variable { id: 0, bounds: (0,1) },
894                    expression: Some(
895                        AtLeast {
896                            ids: vec![1,2,3],
897                            bias: -3,
898                            sign: Sign::Positive,
899                        }
900                    )
901                },
902                Statement {
903                    variable: Variable { id: 1, bounds: (0,1) },
904                    expression: Some(
905                        AtLeast {
906                            ids: vec![4,5],
907                            bias: -1,
908                            sign: Sign::Positive,
909                        }
910                    )
911                },
912                Statement {
913                    variable: Variable { id: 2, bounds: (0,1) },
914                    expression: Some(
915                        AtLeast {
916                            ids: vec![5,6],
917                            bias: -1,
918                            sign: Sign::Positive,
919                        }
920                    )
921                },
922                Statement {
923                    variable: Variable { id: 3, bounds: (0,1) },
924                    expression: Some(
925                        AtLeast {
926                            ids: vec![7,8],
927                            bias: -2,
928                            sign: Sign::Positive,
929                        }
930                    )
931                },
932                Statement {
933                    variable: Variable { id: 4, bounds: (0,1) },
934                    expression: Some(
935                        AtLeast {
936                            ids: vec![9,10],
937                            bias: -2,
938                            sign: Sign::Positive,
939                        }
940                    )
941                },
942                Statement {
943                    variable: Variable { id: 5, bounds: (0,1) },
944                    expression: Some(
945                        AtLeast {
946                            ids: vec![11],
947                            bias: 0,
948                            sign: Sign::Negative,
949                        }
950                    )
951                },
952                Statement {
953                    variable: Variable { id: 6, bounds: (0,1) },
954                    expression: Some(
955                        AtLeast {
956                            ids: vec![12,13],
957                            bias: -1,
958                            sign: Sign::Positive,
959                        }
960                    )
961                },
962                Statement {
963                    variable: Variable { id: 7, bounds: (0,1) },
964                    expression: None
965                },
966                Statement {
967                    variable: Variable { id: 8, bounds: (0,1) },
968                    expression: None
969                },
970                Statement {
971                    variable: Variable { id: 9, bounds: (0,1) },
972                    expression: None
973                },
974                Statement {
975                    variable: Variable { id: 10, bounds: (0,1) },
976                    expression: None
977                },
978                Statement {
979                    variable: Variable { id: 11, bounds: (0,1) },
980                    expression: None
981                },
982                Statement {
983                    variable: Variable { id: 12, bounds: (0,1) },
984                    expression: None
985                },
986                Statement {
987                    variable: Variable { id: 13, bounds: (0,1) },
988                    expression: None
989                },
990            ]
991        };
992        assert!(validate(t));
993
994        // Depth 3
995        // 0: 1 -> 2
996        // 1: 3 & 4
997        // 2: 5 & 6 & 7
998        // Expects to be reduced into 1 constraint
999        // -3(3)-3(4)+(5)+(6)+(7)-3 >= 0 
1000
1001        let t = Theory {
1002            id: String::from("A"),
1003            statements: vec![
1004                Statement {
1005                    variable: Variable { id: 0, bounds: (0,1) },
1006                    expression: Some(
1007                        AtLeast {
1008                            ids: vec![1,2],
1009                            bias: -1,
1010                            sign: Sign::Positive,
1011                        }
1012                    )
1013                },
1014                Statement {
1015                    variable: Variable { id: 1, bounds: (0,1) },
1016                    expression: Some(
1017                        AtLeast {
1018                            ids: vec![3,4],
1019                            bias: 1,
1020                            sign: Sign::Negative,
1021                        }
1022                    )
1023                },
1024                Statement {
1025                    variable: Variable { id: 2, bounds: (0,1) },
1026                    expression: Some(
1027                        AtLeast {
1028                            ids: vec![5,6,7],
1029                            bias: -3,
1030                            sign: Sign::Positive,
1031                        }
1032                    )
1033                },
1034                Statement {
1035                    variable: Variable { id: 3, bounds: (0,1) },
1036                    expression: None
1037                },
1038                Statement {
1039                    variable: Variable { id: 4, bounds: (0,1) },
1040                    expression: None
1041                },
1042                Statement {
1043                    variable: Variable { id: 5, bounds: (0,1) },
1044                    expression: None
1045                },
1046                Statement {
1047                    variable: Variable { id: 6, bounds: (0,1) },
1048                    expression: None
1049                },
1050                Statement {
1051                    variable: Variable { id: 7, bounds: (0,1) },
1052                    expression: None
1053                },
1054            ]
1055        };
1056        assert!(validate(t));
1057        
1058        // Depth 3
1059        // 0: 1 & 2
1060        // 1: -3 | -4
1061        // 2: 5 & 6 & 7
1062        // Expects to be reduced into 1 constraint
1063        // -4(3)-4(4)+(5)+(6)+(7) -3 >= 0
1064
1065        let t = Theory {
1066            id: String::from("A"),
1067            statements: vec![
1068                Statement {
1069                    variable: Variable { id: 0, bounds: (0,1) },
1070                    expression: Some(
1071                        AtLeast {
1072                            ids: vec![1,2],
1073                            bias: -2,
1074                            sign: Sign::Positive,
1075                        }
1076                    )
1077                },
1078                Statement {
1079                    variable: Variable { id: 1, bounds: (0,1) },
1080                    expression: Some(
1081                        AtLeast {
1082                            ids: vec![3,4],
1083                            bias: 0,
1084                            sign: Sign::Negative,
1085                        }
1086                    )
1087                },
1088                Statement {
1089                    variable: Variable { id: 2, bounds: (0,1) },
1090                    expression: Some(
1091                        AtLeast {
1092                            ids: vec![5,6,7],
1093                            bias: -3,
1094                            sign: Sign::Positive,
1095                        }
1096                    )
1097                },
1098                Statement {
1099                    variable: Variable { id: 3, bounds: (0,1) },
1100                    expression: None
1101                },
1102                Statement {
1103                    variable: Variable { id: 4, bounds: (0,1) },
1104                    expression: None
1105                },
1106                Statement {
1107                    variable: Variable { id: 5, bounds: (0,1) },
1108                    expression: None
1109                },
1110                Statement {
1111                    variable: Variable { id: 6, bounds: (0,1) },
1112                    expression: None
1113                },
1114                Statement {
1115                    variable: Variable { id: 7, bounds: (0,1) },
1116                    expression: None
1117                },
1118            ]
1119        };
1120        assert!(validate(t));
1121        
1122        // Depth 4
1123        // 0: 1 & 2
1124        // 1: 3 & 4
1125        // 2: 5 | 6
1126        // 3: 7 | 8
1127        // 4: 9 | 10
1128        // 5: -11
1129        // 6: 12 & 13
1130
1131        // Expects to be reduced into 5 constraints
1132        // Notice since 0 constraint has 2 subs, it gets
1133        // reduced before 1 and 2 could be reduced. Because
1134        // of that, we don't get a minimum number of constraints.
1135        //  3( 3) +3( 4) +( 5) +( 6) +( 7) -7
1136        // -2( 6) +3(12) +(13) +0
1137        // -1( 5) -1(11) +1
1138        // -1( 4) +1( 9) +(10) +0
1139        // -1( 3) +1( 7) +( 8) +0
1140        let t = Theory {
1141            id: String::from("A"),
1142            statements: vec![
1143                Statement {
1144                    variable: Variable { id: 0, bounds: (0,1) },
1145                    expression: Some(
1146                        AtLeast {
1147                            ids: vec![1,2],
1148                            bias: -2,
1149                            sign: Sign::Positive,
1150                        }
1151                    )
1152                },
1153                Statement {
1154                    variable: Variable { id: 1, bounds: (0,1) },
1155                    expression: Some(
1156                        AtLeast {
1157                            ids: vec![3,4],
1158                            bias: -2,
1159                            sign: Sign::Positive,
1160                        }
1161                    )
1162                },
1163                Statement {
1164                    variable: Variable { id: 2, bounds: (0,1) },
1165                    expression: Some(
1166                        AtLeast {
1167                            ids: vec![5,6],
1168                            bias: -1,
1169                            sign: Sign::Positive,
1170                        }
1171                    )
1172                },
1173                Statement {
1174                    variable: Variable { id: 3, bounds: (0,1) },
1175                    expression: Some(
1176                        AtLeast {
1177                            ids: vec![7,8],
1178                            bias: -1,
1179                            sign: Sign::Positive,
1180                        }
1181                    )
1182                },
1183                Statement {
1184                    variable: Variable { id: 4, bounds: (0,1) },
1185                    expression: Some(
1186                        AtLeast {
1187                            ids: vec![9,10],
1188                            bias: -1,
1189                            sign: Sign::Positive,
1190                        }
1191                    )
1192                },
1193                Statement {
1194                    variable: Variable { id: 5, bounds: (0,1) },
1195                    expression: Some(
1196                        AtLeast {
1197                            ids: vec![11],
1198                            bias: 0,
1199                            sign: Sign::Negative,
1200                        }
1201                    )
1202                },
1203                Statement {
1204                    variable: Variable { id: 6, bounds: (0,1) },
1205                    expression: Some(
1206                        AtLeast {
1207                            ids: vec![12,13],
1208                            bias: -2,
1209                            sign: Sign::Positive,
1210                        }
1211                    )
1212                },
1213                Statement {
1214                    variable: Variable { id: 7, bounds: (0,1) },
1215                    expression: None
1216                },
1217                Statement {
1218                    variable: Variable { id: 8, bounds: (0,1) },
1219                    expression: None
1220                },
1221                Statement {
1222                    variable: Variable { id: 9, bounds: (0,1) },
1223                    expression: None
1224                },
1225                Statement {
1226                    variable: Variable { id: 10, bounds: (0,1) },
1227                    expression: None
1228                },
1229                Statement {
1230                    variable: Variable { id: 11, bounds: (0,1) },
1231                    expression: None
1232                },
1233                Statement {
1234                    variable: Variable { id: 12, bounds: (0,1) },
1235                    expression: None
1236                },
1237                Statement {
1238                    variable: Variable { id: 13, bounds: (0,1) },
1239                    expression: None
1240                },
1241            ]
1242        };
1243        assert!(validate(t));
1244    }
1245
1246    #[test]
1247    fn test_merge_conj_disj(){
1248
1249        fn validate_all_combinations_single(ge_lineq1: GeLineq, ge_lineq2: GeLineq, conj: bool) -> bool {
1250            let mut variable_bounds_hm: HashMap<u32, (i64,i64)> = HashMap::default();
1251            for lineq in vec![ge_lineq1.clone(), ge_lineq2.clone()] {
1252                for (id, bound) in lineq.indices.iter().zip(lineq.bounds) {
1253                    variable_bounds_hm.insert(*id, bound);
1254                }
1255            }
1256            let variable_bounds = variable_bounds_hm.into_iter().collect();
1257            if conj {
1258                if let Some(merged) = GeLineq::merge_conj(&ge_lineq1, &ge_lineq2) {
1259                    return validate_all_combinations(
1260                        variable_bounds, 
1261                        vec![ge_lineq1, ge_lineq2], 
1262                        vec![merged], 
1263                        true
1264                    );
1265                }
1266                return false;
1267            } else {
1268                if let Some(merged) = GeLineq::merge_disj(&ge_lineq1, &ge_lineq2) {
1269                    return validate_all_combinations(
1270                        variable_bounds, 
1271                        vec![ge_lineq1, ge_lineq2], 
1272                        vec![merged], 
1273                        false
1274                    );
1275                }
1276                return false;
1277            }
1278        }
1279
1280        // Disjunction merge between 1-1
1281        let ge_lineq1:GeLineq = GeLineq {
1282            id: None,
1283            coeffs  : vec![1, 1, 1],
1284            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1285            bias    : -1,
1286            indices : vec![1, 2, 3]
1287        };
1288        let ge_lineq2: GeLineq = GeLineq {
1289            id: None,
1290            coeffs  : vec![1, 1],
1291            bounds  : vec![(0, 1), (0, 1)],
1292            bias    : -1,
1293            indices : vec![1, 6]
1294        };
1295        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1296        
1297        // }
1298        // Disjunction merge between 2-3
1299        let ge_lineq1:GeLineq = GeLineq {
1300            id: None,
1301            coeffs  : vec![-1, -1, -1],
1302            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1303            bias    : 2,
1304            indices : vec![1, 2, 3]
1305        };
1306        let ge_lineq2: GeLineq = GeLineq {
1307            id: None,
1308            coeffs  : vec![-1, -1, -1, -1],
1309            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1310            bias    : 0,
1311            indices : vec![1, 2, 3, 8]
1312        };
1313        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1314
1315        // Disjunction merge between 1-1
1316        let ge_lineq1:GeLineq = GeLineq {
1317            id: None,
1318            coeffs  : vec![1, 1, 1],
1319            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1320            bias    : -1,
1321            indices : vec![1, 2, 3]
1322        };
1323        let ge_lineq2: GeLineq = GeLineq {
1324            id: None,
1325            coeffs  : vec![1, 1],
1326            bounds  : vec![(0, 1), (0, 1)],
1327            bias    : 0,
1328            indices : vec![5, 6]
1329        };
1330        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1331
1332        // Disjunction merge between 1-2
1333        let ge_lineq1:GeLineq = GeLineq {
1334            id: None,
1335            coeffs  : vec![1, 1, 1],
1336            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1337            bias    : -1,
1338            indices : vec![1, 2, 3]
1339        };
1340        let ge_lineq2: GeLineq = GeLineq {
1341            id: None,
1342            coeffs  : vec![-1, -1, -1, -1],
1343            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1344            bias    : 3,
1345            indices : vec![5, 6, 7, 8]
1346        };
1347        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1348
1349        // Disjunction merge between 1-3
1350        let ge_lineq1:GeLineq = GeLineq {
1351            id: None,
1352            coeffs  : vec![1, 1, 1],
1353            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1354            bias    : -1,
1355            indices : vec![1, 2, 3]
1356        };
1357        let ge_lineq2: GeLineq = GeLineq {
1358            id: None,
1359            coeffs  : vec![-1, -1, -1, -1],
1360            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1361            bias    : 0,
1362            indices : vec![5, 6, 7, 8]
1363        };
1364        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1365
1366        // Disjunction merge between 1-4
1367        let ge_lineq1:GeLineq = GeLineq {
1368            id: None,
1369            coeffs  : vec![1, 1, 1],
1370            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1371            bias    : -1,
1372            indices : vec![1, 2, 3]
1373        };
1374        let ge_lineq2: GeLineq = GeLineq {
1375            id: None,
1376            coeffs  : vec![1, 1, 1, 1],
1377            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1378            bias    : -4,
1379            indices : vec![5, 6, 7, 8]
1380        };
1381        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1382
1383        // Disjunction merge between 1-5
1384        let ge_lineq1:GeLineq = GeLineq {
1385            id: None,
1386            coeffs  : vec![1, 1, 1],
1387            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1388            bias    : -1,
1389            indices : vec![1, 2, 3]
1390        };
1391        let ge_lineq2: GeLineq = GeLineq {
1392            id: None,
1393            coeffs  : vec![1, 1, 1, 1],
1394            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1395            bias    : -2,
1396            indices : vec![5, 6, 7, 8]
1397        };
1398        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1399
1400        // Disjunction merge between 1-6
1401        let ge_lineq1:GeLineq = GeLineq {
1402            id: None,
1403            coeffs  : vec![1, 1, 1],
1404            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1405            bias    : -1,
1406            indices : vec![1, 2, 3]
1407        };
1408        let ge_lineq2: GeLineq = GeLineq {
1409            id: None,
1410            coeffs  : vec![-1, -1, -1, -1],
1411            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1412            bias    : 2,
1413            indices : vec![5, 6, 7, 8]
1414        };
1415        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1416
1417        // Disjunction merge between 2-2
1418        let ge_lineq1:GeLineq = GeLineq {
1419            id: None,
1420            coeffs  : vec![-1, -1, -1],
1421            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1422            bias    : 2,
1423            indices : vec![1, 2, 3]
1424        };
1425        let ge_lineq2: GeLineq = GeLineq {
1426            id: None,
1427            coeffs  : vec![-1, -1, -1, -1],
1428            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1429            bias    : 3,
1430            indices : vec![5, 6, 7, 8]
1431        };
1432        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1433
1434        // Disjunction merge between 2-3
1435        let ge_lineq1:GeLineq = GeLineq {
1436            id: None,
1437            coeffs  : vec![-1, -1, -1],
1438            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1439            bias    : 2,
1440            indices : vec![1, 2, 3]
1441        };
1442        let ge_lineq2: GeLineq = GeLineq {
1443            id: None,
1444            coeffs  : vec![-1, -1, -1, -1],
1445            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1446            bias    : 0,
1447            indices : vec![5, 6, 7, 8]
1448        };
1449        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1450
1451        // Disjunction merge between 2-4
1452        let ge_lineq1:GeLineq = GeLineq {
1453            id: None,
1454            coeffs  : vec![-1, -1, -1],
1455            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1456            bias    : 2,
1457            indices : vec![1, 2, 3]
1458        };
1459        let ge_lineq2: GeLineq = GeLineq {
1460            id: None,
1461            coeffs  : vec![1, 1, 1, 1],
1462            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1463            bias    : -4,
1464            indices : vec![5, 6, 7, 8]
1465        };
1466        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1467
1468        // Disjunction merge between 2-5
1469        let ge_lineq1:GeLineq = GeLineq {
1470            id: None,
1471            coeffs  : vec![-1, -1, -1],
1472            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1473            bias    : 2,
1474            indices : vec![1, 2, 3]
1475        };
1476        let ge_lineq2: GeLineq = GeLineq {
1477            id: None,
1478            coeffs  : vec![1, 1, 1, 1],
1479            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1480            bias    : -2,
1481            indices : vec![5, 6, 7, 8]
1482        };
1483        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1484
1485        // Disjunction merge between 2-6
1486        let ge_lineq1:GeLineq = GeLineq {
1487            id: None,
1488            coeffs  : vec![-1, -1, -1],
1489            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1490            bias    : 2,
1491            indices : vec![1, 2, 3]
1492        };
1493        let ge_lineq2: GeLineq = GeLineq {
1494            id: None,
1495            coeffs  : vec![-1, -1, -1, -1],
1496            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1497            bias    : 2,
1498            indices : vec![5, 6, 7, 8]
1499        };
1500        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1501
1502        // Conjunction merge between 4-4
1503        let ge_lineq1:GeLineq = GeLineq {
1504            id: None,
1505            coeffs  : vec![1, 1, 1],
1506            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1507            bias    : -3,
1508            indices : vec![1, 2, 3]
1509        };
1510        let ge_lineq2: GeLineq = GeLineq {
1511            id: None,
1512            coeffs  : vec![1, 1, 1],
1513            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1514            bias    : -3,
1515            indices : vec![1, 0, 4]
1516        };
1517        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1518
1519        // Conjunction merge between 4-3
1520        let ge_lineq1:GeLineq = GeLineq {
1521            id: None,
1522            coeffs  : vec![1, 1, 1],
1523            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1524            bias    : -3,
1525            indices : vec![1, 2, 3]
1526        };
1527        let ge_lineq2: GeLineq = GeLineq {
1528            id: None,
1529            coeffs  : vec![-1, -1, -1, -1],
1530            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1531            bias    : 0,
1532            indices : vec![5, 6, 7, 8]
1533        };
1534        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1535
1536        // Conjunction merge between 1-4
1537        let ge_lineq1:GeLineq = GeLineq {
1538            id: None,
1539            coeffs  : vec![1, 1, 1],
1540            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1541            bias    : -1,
1542            indices : vec![1, 2, 3]
1543        };
1544        let ge_lineq2: GeLineq = GeLineq {
1545            id: None,
1546            coeffs  : vec![1, 1, 1, 1],
1547            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1548            bias    : -4,
1549            indices : vec![5, 6, 7, 8]
1550        };
1551        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1552
1553        // Conjunction merge between 2-3
1554        let ge_lineq1:GeLineq = GeLineq {
1555            id: None,
1556            coeffs  : vec![-1, -1, -1],
1557            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1558            bias    : 2,
1559            indices : vec![1, 2, 3]
1560        };
1561        let ge_lineq2: GeLineq = GeLineq {
1562            id: None,
1563            coeffs  : vec![-1, -1, -1, -1],
1564            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1565            bias    : 0,
1566            indices : vec![5, 6, 7, 8]
1567        };
1568        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1569
1570        // Conjunction merge between 2-4
1571        let ge_lineq1:GeLineq = GeLineq {
1572            id: None,
1573            coeffs  : vec![-1, -1, -1],
1574            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1575            bias    : 2,
1576            indices : vec![1, 2, 3]
1577        };
1578        let ge_lineq2: GeLineq = GeLineq {
1579            id: None,
1580            coeffs  : vec![1, 1, 1, 1],
1581            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1582            bias    : -4,
1583            indices : vec![5, 6, 7, 8]
1584        };
1585        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1586
1587        // Conjunction merge between 3-3
1588        let ge_lineq1:GeLineq = GeLineq {
1589            id: None,
1590            coeffs  : vec![-1, -1, -1],
1591            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1592            bias    : 0,
1593            indices : vec![1, 2, 3]
1594        };
1595        let ge_lineq2: GeLineq = GeLineq {
1596            id: None,
1597            coeffs  : vec![-1, -1, -1, -1],
1598            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1599            bias    : 0,
1600            indices : vec![5, 6, 7, 8]
1601        };
1602        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1603
1604        // Conjunction merge between 3-4
1605        let ge_lineq1:GeLineq = GeLineq {
1606            id: None,
1607            coeffs  : vec![-1, -1, -1],
1608            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1609            bias    : 0,
1610            indices : vec![1, 2, 3]
1611        };
1612        let ge_lineq2: GeLineq = GeLineq {
1613            id: None,
1614            coeffs  : vec![1, 1, 1, 1],
1615            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1616            bias    : -4,
1617            indices : vec![5, 6, 7, 8]
1618        };
1619        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1620
1621        // Conjunction merge between 3-5
1622        let ge_lineq1:GeLineq = GeLineq {
1623            id: None,
1624            coeffs  : vec![-1, -1, -1],
1625            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1626            bias    : 0,
1627            indices : vec![1, 2, 3]
1628        };
1629        let ge_lineq2: GeLineq = GeLineq {
1630            id: None,
1631            coeffs  : vec![1, 1, 1, 1],
1632            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1633            bias    : -2,
1634            indices : vec![5, 6, 7, 8]
1635        };
1636        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1637
1638        // Conjunction merge between 3-6
1639        let ge_lineq1:GeLineq = GeLineq {
1640            id: None,
1641            coeffs  : vec![-1, -1, -1],
1642            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1643            bias    : 0,
1644            indices : vec![1, 2, 3]
1645        };
1646        let ge_lineq2: GeLineq = GeLineq {
1647            id: None,
1648            coeffs  : vec![-1, -1, -1, -1],
1649            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1650            bias    : 2,
1651            indices : vec![5, 6, 7, 8]
1652        };
1653        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1654
1655        // Conjunction merge between 4-4
1656        let ge_lineq1:GeLineq = GeLineq {
1657            id: None,
1658            coeffs  : vec![1, 1, 1],
1659            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1660            bias    : -3,
1661            indices : vec![1, 2, 3]
1662        };
1663        let ge_lineq2: GeLineq = GeLineq {
1664            id: None,
1665            coeffs  : vec![1, 1, 1, 1],
1666            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1667            bias    : -4,
1668            indices : vec![5, 6, 7, 8]
1669        };
1670        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1671
1672        // Conjunction merge between 4-5
1673        let ge_lineq1:GeLineq = GeLineq {
1674            id: None,
1675            coeffs  : vec![1, 1, 1],
1676            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1677            bias    : -3,
1678            indices : vec![1, 2, 3]
1679        };
1680        let ge_lineq2: GeLineq = GeLineq {
1681            id: None,
1682            coeffs  : vec![1, 1, 1, 1],
1683            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1684            bias    : -2,
1685            indices : vec![5, 6, 7, 8]
1686        };
1687        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1688
1689        // Conjunction merge between 4-6
1690        let ge_lineq1:GeLineq = GeLineq {
1691            id: None,
1692            coeffs  : vec![1, 1, 1],
1693            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1694            bias    : -3,
1695            indices : vec![1, 2, 3]
1696        };
1697        let ge_lineq2: GeLineq = GeLineq {
1698            id: None,
1699            coeffs  : vec![-1, -1, -1, -1],
1700            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1701            bias    : 2,
1702            indices : vec![5, 6, 7, 8]
1703        };
1704        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1705
1706        // Conjunction merge between 4-4
1707        let ge_lineq1:GeLineq = GeLineq {
1708            id: None,
1709            coeffs  : vec![1, 1, 1],
1710            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1711            bias    : 3,
1712            indices : vec![1, 2, 3]
1713        };
1714        let ge_lineq2: GeLineq = GeLineq {
1715            id: None,
1716            coeffs  : vec![1, 1, 1, 1],
1717            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1718            bias    : -4,
1719            indices : vec![5, 6, 7, 8]
1720        };
1721        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1722
1723        // Disjunction merge, special case
1724        let ge_lineq1:GeLineq = GeLineq {
1725            id: None,
1726            coeffs  : vec![1, 1, 1],
1727            bounds  : vec![(-2, -1), (2, 3), (2, 3)],
1728            bias    : -3,
1729            indices : vec![1, 2, 3]
1730        };
1731        let ge_lineq2: GeLineq = GeLineq {
1732            id: None,
1733            coeffs  : vec![1, 1, 1, 1],
1734            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1735            bias    : -1,
1736            indices : vec![5, 6, 7, 8]
1737        };
1738        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, false));
1739
1740        // Conjunction merge, special case
1741        let ge_lineq1:GeLineq = GeLineq {
1742            id: None,
1743            coeffs  : vec![1, 1, 1],
1744            bounds  : vec![(-2, -1), (2, 3), (2, 3)],
1745            bias    : -5,
1746            indices : vec![1, 2, 3]
1747        };
1748        let ge_lineq2: GeLineq = GeLineq {
1749            id: None,
1750            coeffs  : vec![1, 1, 1, 1],
1751            bounds  : vec![(0, 1), (0, 1), (0, 1), (0, 1)],
1752            bias    : -1,
1753            indices : vec![5, 6, 7, 8]
1754        };
1755        assert!(validate_all_combinations_single(ge_lineq1, ge_lineq2, true));
1756    }
1757    
1758    #[test]
1759    fn test_substitution() {
1760
1761        fn validate_substitution(main_lineq: GeLineq, sub_lineq: GeLineq, variable_index: u32) -> bool {
1762            if let Some(result) = GeLineq::substitution(&main_lineq, variable_index, &sub_lineq) {
1763                let mut variable_bounds_hm: HashMap<u32, (i64,i64)> = HashMap::default();
1764                for lineq in vec![main_lineq.clone(), sub_lineq.clone()] {
1765                    for (id, bound) in lineq.indices.iter().zip(lineq.bounds) {
1766                        if (*id) != variable_index {
1767                            variable_bounds_hm.insert(*id, bound);
1768                        }
1769                    }
1770                }
1771                let variable_bounds: Vec<(u32, (i64, i64))> = variable_bounds_hm.into_iter().collect();
1772                let variable_ids : Vec<u32> = variable_bounds.iter().map(|x| x.0).collect_vec();
1773                let bound_ranges: Vec<std::ops::Range<i64>> = variable_bounds.iter().map(|x| Range {start: x.1.0, end: x.1.1+1}).collect_vec();
1774                return bound_ranges.into_iter().multi_cartesian_product().all(|combination| {
1775                    let mut main_variable_combination: Vec<(u32, i64)> = variable_ids.clone().into_iter().zip(combination.clone()).filter(|(id, _)| {
1776                        main_lineq.indices.contains(id)
1777                    }).collect();
1778                    let sub_variable_combination: Vec<(u32, i64)> = variable_ids.clone().into_iter().zip(combination.clone()).filter(|(id, _)| {
1779                        sub_lineq.indices.contains(id)
1780                    }).collect();
1781                    let sub_value: i64 = sub_lineq.satisfied(sub_variable_combination) as i64;
1782                    main_variable_combination.push((variable_index, sub_value));
1783
1784                    let main_result = main_lineq.satisfied(main_variable_combination);
1785                    let substitution_result = result.satisfied(variable_ids.clone().into_iter().zip(combination.clone()).collect());
1786
1787                    return (!main_result & !substitution_result) | (main_result & substitution_result);
1788                });
1789            }
1790
1791            return false;
1792        }
1793
1794        // Negative bias on sub lineq
1795        let main_gelineq:GeLineq = GeLineq {
1796            id: None,
1797            coeffs  : vec![1, 1, 1],
1798            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1799            bias    : -2,
1800            indices : vec![1, 2, 3]
1801        };
1802        let sub_gelineq: GeLineq = GeLineq {
1803            id: None,
1804            coeffs  : vec![1, 1],
1805            bounds  : vec![(0, 1), (0, 1)],
1806            bias    : -2,
1807            indices : vec![4,5]
1808        };
1809        assert!(validate_substitution(main_gelineq, sub_gelineq, 3));
1810
1811        let main_gelineq:GeLineq = GeLineq {
1812            id: None,
1813            coeffs  : vec![1, 1, 1],
1814            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1815            bias    : -2,
1816            indices : vec![1, 2, 3]
1817        };
1818        let sub_gelineq: GeLineq = GeLineq {
1819            id: None,
1820            coeffs  : vec![1, 1],
1821            bounds  : vec![(0, 1), (0, 1)],
1822            bias    : -1,
1823            indices : vec![4,5]
1824        };
1825        assert!(validate_substitution(main_gelineq, sub_gelineq, 3));
1826
1827        // Positive bias on sub lineq
1828        let main_gelineq:GeLineq = GeLineq {
1829            id: None,
1830            coeffs  : vec![1, 1, 1],
1831            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1832            bias    : -2,
1833            indices : vec![1, 2, 3]
1834        };
1835        let sub_gelineq: GeLineq = GeLineq {
1836            id: None,
1837            coeffs  : vec![-1, -1],
1838            bounds  : vec![(0, 1), (0, 1)],
1839            bias    : 1,
1840            indices : vec![4,5]
1841        };
1842        assert!(validate_substitution(main_gelineq, sub_gelineq, 3));
1843
1844        // Not possible to substitute
1845        let main_gelineq:GeLineq = GeLineq {
1846            id: None,
1847            coeffs  : vec![1, 1, 1],
1848            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1849            bias    : -2,
1850            indices : vec![1, 2, 3]
1851        };
1852        let sub_gelineq: GeLineq = GeLineq {
1853            id: None,
1854            coeffs  : vec![-1, 1, 1],
1855            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1856            bias    : 0,
1857            indices : vec![4,5, 6]
1858        };
1859        assert!(!validate_substitution(main_gelineq, sub_gelineq, 3));
1860
1861        let main_gelineq:GeLineq = GeLineq {
1862            id: None,
1863            coeffs  : vec![-1],
1864            bounds  : vec![(0, 1)],
1865            bias    : 0,
1866            indices : vec![1]
1867        };
1868        let sub_gelineq: GeLineq = GeLineq {
1869            id: None,
1870            coeffs  : vec![1],
1871            bounds  : vec![(0, 10)],
1872            bias    : -3,
1873            indices : vec![2]
1874        };
1875        assert!(validate_substitution(main_gelineq.clone(), sub_gelineq.clone(), 1));
1876        let result = GeLineq::substitution(&main_gelineq, 1, &sub_gelineq);
1877        assert_eq!(vec![-1], result.as_ref().expect("").coeffs);
1878        assert_eq!(vec![(0, 10)], result.as_ref().expect("").bounds);
1879        assert_eq!(2, result.as_ref().expect("").bias);
1880
1881        // Mixed signs of coeffs in sub lineq, possible to merge
1882        let main_gelineq:GeLineq = GeLineq {
1883            id: None,
1884            coeffs  : vec![1, 1, 1],
1885            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1886            bias    : -2,
1887            indices : vec![1, 2, 3]
1888        };
1889        let sub_gelineq: GeLineq = GeLineq {
1890            id: None,
1891            coeffs  : vec![-1, 1, 1],
1892            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1893            bias    : -1,
1894            indices : vec![4,5, 6]
1895        };
1896        assert!(validate_substitution(main_gelineq, sub_gelineq, 3));
1897
1898        let main_gelineq:GeLineq = GeLineq {
1899            id: None,
1900            coeffs  : vec![1, 1],
1901            bounds  : vec![(0, 1), (0, 1)],
1902            bias    : -2,
1903            indices : vec![1, 2]
1904        };
1905        let sub_gelineq: GeLineq = GeLineq {
1906            id: None,
1907            coeffs  : vec![1, 1],
1908            bounds  : vec![(0, 1), (0, 1)],
1909            bias    : -1,
1910            indices : vec![3, 4]
1911        };
1912        assert!(validate_substitution(main_gelineq, sub_gelineq, 2));
1913
1914        let main_gelineq:GeLineq = GeLineq {
1915            id: None,
1916            coeffs  : vec![1, 1],
1917            bounds  : vec![(0, 1), (0, 1)],
1918            bias    : -1,
1919            indices : vec![1, 2]
1920        };
1921        let sub_gelineq: GeLineq = GeLineq {
1922            id: None,
1923            coeffs  : vec![1, 1],
1924            bounds  : vec![(0, 1), (0, 1)],
1925            bias    : -1,
1926            indices : vec![3, 4]
1927        };
1928        assert!(validate_substitution(main_gelineq, sub_gelineq, 2));
1929
1930        let main_gelineq:GeLineq = GeLineq {
1931            id: None,
1932            coeffs  : vec![1, 1, 1],
1933            bounds  : vec![(0, 1), (0, 1), (0, 1)],
1934            bias    : -2,
1935            indices : vec![1, 2, 3]
1936        };
1937        let sub_gelineq1: GeLineq = GeLineq {
1938            id: None,
1939            coeffs  : vec![1, 1],
1940            bounds  : vec![(0, 1), (0, 1)],
1941            bias    : -2,
1942            indices : vec![4, 5]
1943        };
1944        let sub_gelineq2: GeLineq = GeLineq {
1945            id: None,
1946            coeffs  : vec![1, 1],
1947            bounds  : vec![(0, 1), (0, 1)],
1948            bias    : -2,
1949            indices : vec![6, 7]
1950        };
1951        let result1 = GeLineq::substitution(&main_gelineq, 2, &sub_gelineq1);
1952        assert!(validate_substitution(main_gelineq, sub_gelineq1, 2));
1953        let result2 = GeLineq::substitution(&result1.as_ref().expect("No gelineq created"), 3, &sub_gelineq2);
1954        assert!(result2.is_none());
1955    }
1956
1957    #[test]
1958    fn test_min_max_coefficients() {
1959
1960        fn validate_all_combinations_single_min_max(gelineq: GeLineq, expected_vec: Option<Vec<i64>>) -> bool {
1961            let mut variable_bounds_hm: HashMap<u32, (i64,i64)> = HashMap::default();
1962            for (id, bound) in gelineq.indices.iter().zip(gelineq.bounds.clone()) {
1963                variable_bounds_hm.insert(*id, bound);
1964            }
1965            let variable_bounds = variable_bounds_hm.into_iter().collect();
1966            if let Some(result) = GeLineq::min_max_coefficients(&gelineq) {
1967                let validation = validate_all_combinations(
1968                    variable_bounds, 
1969                    vec![gelineq], 
1970                    vec![result.clone()], 
1971                    true
1972                );
1973                if let Some(vec) = expected_vec {
1974                    return validation & (vec == result.coeffs)
1975                } 
1976                return validation;
1977            }
1978            return false;
1979        }
1980
1981        assert!(
1982            validate_all_combinations_single_min_max(
1983                GeLineq {
1984                    id: None, 
1985                    coeffs: vec![2, 1, 1],
1986                    bounds: vec![(0,1),(0,1),(0,1)],
1987                    bias: -1,
1988                    indices: vec![0, 1, 2]
1989                }, 
1990                Some(vec![1, 1, 1])
1991            )
1992        );
1993        assert!(
1994            !validate_all_combinations_single_min_max(
1995                GeLineq {
1996                    id: None, 
1997                    coeffs: vec![2, 1, 1],
1998                    bounds: vec![(-2,-1),(0,1),(0,1)],
1999                    bias: 0,
2000                    indices: vec![0, 1, 2]
2001                }, 
2002                None
2003            )
2004        );
2005        assert!(
2006            validate_all_combinations_single_min_max(
2007                GeLineq {
2008                    id: None, 
2009                    coeffs: vec![5, 6, 3],
2010                    bounds: vec![(0,1),(0,1),(0,1)],
2011                    bias: -1,
2012                    indices: vec![0, 1, 2]
2013                }, 
2014                Some(vec![1, 1, 1])
2015            )
2016        );
2017        assert!(
2018            validate_all_combinations_single_min_max(
2019                GeLineq {
2020                    id: None, 
2021                    coeffs: vec![-2, -1, -1],
2022                    bounds: vec![(0,1),(0,1),(0,1)],
2023                    bias: 0,
2024                    indices: vec![0, 1, 2]
2025                }, 
2026                Some(vec![-1, -1, -1]),
2027            )
2028        );
2029        assert!(
2030            validate_all_combinations_single_min_max(
2031                GeLineq {
2032                    id: None, 
2033                    coeffs: vec![-2, -1, -1],
2034                    bounds: vec![(0,1),(0,1),(0,1)],
2035                    bias: 1,
2036                    indices: vec![0, 1, 2]
2037                }, 
2038                Some(vec![-2,-1,-1]),
2039            )
2040        );
2041        assert!(
2042            !validate_all_combinations_single_min_max(
2043                GeLineq {
2044                    id: None, 
2045                    coeffs: vec![-2, 1, 1],
2046                    bounds: vec![(0,1),(0,1),(0,1)],
2047                    bias: 0,
2048                    indices: vec![0, 1, 2]
2049                }, 
2050                None
2051            )
2052        );
2053    }
2054
2055    #[test]
2056    fn test_theory_top_ok() {
2057        let t = Theory {
2058            id: String::from("A"),
2059            statements: vec![
2060                Statement {
2061                    variable: Variable { id: 0, bounds: (0,1) },
2062                    expression: Some(
2063                        AtLeast {
2064                            ids: vec![1,2],
2065                            bias: -1,
2066                            sign: Sign::Positive,
2067                        }
2068                    )
2069                },
2070                Statement {
2071                    variable: Variable { id: 1, bounds: (0,1) },
2072                    expression: Some(
2073                        AtLeast {
2074                            ids: vec![3,4],
2075                            bias: 1,
2076                            sign: Sign::Negative,
2077                        }
2078                    )
2079                },
2080                Statement {
2081                    variable: Variable { id: 2, bounds: (0,1) },
2082                    expression: Some(
2083                        AtLeast {
2084                            ids: vec![5,6,7],
2085                            bias: -3,
2086                            sign: Sign::Positive,
2087                        }
2088                    )
2089                },
2090            ]
2091        };
2092        assert_eq!(*t._top(), 0);
2093    }
2094
2095    #[test]
2096    #[should_panic]
2097    fn test_theory_top_has_circular_references() {
2098        let t = Theory {
2099            id: String::from("A"),
2100            statements: vec![
2101                Statement {
2102                    variable: Variable { id: 0, bounds: (0,1) },
2103                    expression: Some(
2104                        AtLeast {
2105                            ids: vec![1],
2106                            bias: -1,
2107                            sign: Sign::Positive,
2108                        }
2109                    )
2110                },
2111                Statement {
2112                    variable: Variable { id: 1, bounds: (0,1) },
2113                    expression: Some(
2114                        AtLeast {
2115                            ids: vec![2],
2116                            bias: 1,
2117                            sign: Sign::Negative,
2118                        }
2119                    )
2120                },
2121                Statement {
2122                    variable: Variable { id: 2, bounds: (0,1) },
2123                    expression: Some(
2124                        AtLeast {
2125                            ids: vec![0],
2126                            bias: -3,
2127                            sign: Sign::Positive,
2128                        }
2129                    )
2130                },
2131            ]
2132        };
2133        t._top();
2134    }
2135
2136    #[test]
2137    fn test_theory_top_should_not_panic() {
2138        let t = Theory {
2139            id: String::from("A"),
2140            statements: vec![
2141                Statement {
2142                    variable: Variable { id: 2, bounds: (0,1) },
2143                    expression: None
2144                },
2145                Statement {
2146                    variable: Variable { id: 1, bounds: (0,1) },
2147                    expression: Some(
2148                        AtLeast {
2149                            ids: vec![2],
2150                            bias: 1,
2151                            sign: Sign::Negative,
2152                        }
2153                    )
2154                },
2155                Statement {
2156                    variable: Variable { id: 0, bounds: (0,1) },
2157                    expression: Some(
2158                        AtLeast {
2159                            ids: vec![1],
2160                            bias: -3,
2161                            sign: Sign::Positive,
2162                        }
2163                    )
2164                },
2165            ]
2166        };
2167        t._top();
2168    }
2169
2170    #[test]
2171    fn test_theory_variable_hm_should_be_correct() {
2172        let t = Theory {
2173            id: String::from("A"),
2174            statements: vec![
2175                Statement {
2176                    variable: Variable { id: 0, bounds: (0,1) },
2177                    expression: Some(
2178                        AtLeast {
2179                            ids: vec![1,2],
2180                            bias: -1,
2181                            sign: Sign::Positive,
2182                        }
2183                    )
2184                },
2185                Statement {
2186                    variable: Variable { id: 1, bounds: (0,1) },
2187                    expression: Some(
2188                        AtLeast {
2189                            ids: vec![3,4],
2190                            bias: 1,
2191                            sign: Sign::Negative,
2192                        }
2193                    )
2194                },
2195                Statement {
2196                    variable: Variable { id: 2, bounds: (0,1) },
2197                    expression: Some(
2198                        AtLeast {
2199                            ids: vec![5,6,7],
2200                            bias: -3,
2201                            sign: Sign::Positive,
2202                        }
2203                    )
2204                },
2205                Statement {
2206                    variable: Variable { id: 3, bounds: (0,1) },
2207                    expression: None
2208                },
2209                Statement {
2210                    variable: Variable { id: 4, bounds: (0,1) },
2211                    expression: None
2212                },
2213                Statement {
2214                    variable: Variable { id: 5, bounds: (0,1) },
2215                    expression: None
2216                },
2217                Statement {
2218                    variable: Variable { id: 6, bounds: (0,1) },
2219                    expression: None
2220                },
2221                Statement {
2222                    variable: Variable { id: 7, bounds: (0,1) },
2223                    expression: None
2224                },
2225            ]
2226        };
2227        let vm = t._variable_hm();
2228        let expected_keys : Vec<u32> = vec![0,1,2,3,4,5,6,7];
2229        let test_result = expected_keys.iter().map(|k| vm.contains_key(k)).all(|k| k);
2230        assert!(test_result);
2231    }
2232    
2233    #[test]
2234    fn test_theory_variable_hm_have_id_duplicates() {
2235        let t = Theory {
2236            id: String::from("A"),
2237            statements: vec![
2238                Statement {
2239                    variable: Variable { id: 0, bounds: (0,1) },
2240                    expression: Some(
2241                        AtLeast {
2242                            ids: vec![1,2],
2243                            bias: -1,
2244                            sign: Sign::Positive,
2245                        }
2246                    )
2247                },
2248                Statement {
2249                    variable: Variable { id: 1, bounds: (0,1) },
2250                    expression: None
2251                },
2252                Statement {
2253                    variable: Variable { id: 2, bounds: (0,1) },
2254                    expression: None
2255                },
2256                Statement {
2257                    variable: Variable { id: 0, bounds: (0,1) },
2258                    expression: None
2259                },
2260            ]
2261        };
2262        let vm = t._variable_hm();
2263        let expected_keys : Vec<u32> = vec![0,1,2];
2264        let test_result = expected_keys.iter().map(|k| vm.contains_key(k)).all(|k| k);
2265        assert!(test_result);
2266    }
2267    
2268    #[test]
2269    fn test_theory_to_lineqs() {
2270
2271        fn validate_theory_lineqs(t: Theory, actual: Vec<GeLineq>, expected: Vec<GeLineq>) -> bool {
2272            assert!(actual.iter().zip(expected.clone()).all(|(a,b)| {
2273                let bias_eq = a.bias == b.bias;
2274                let indices_eq = a.indices == b.indices;
2275                let coeffs_eq = a.coeffs == b.coeffs;
2276                let bounds_eq = a.bounds == b.bounds;
2277                bias_eq & indices_eq & coeffs_eq & bounds_eq
2278            }));
2279            return validate_all_combinations(
2280                t.statements.into_iter().map(|x| (x.variable.id, x.variable.bounds)).collect(), 
2281                actual, 
2282                expected, 
2283                true
2284            )
2285        }
2286
2287        let t = Theory {
2288            id: String::from("A"),
2289            statements: vec![
2290                Statement {
2291                    variable: Variable { id: 0, bounds: (0,1) },
2292                    expression: Some(
2293                        AtLeast {
2294                            ids: vec![1,2],
2295                            bias: -1,
2296                            sign: Sign::Positive,
2297                        }
2298                    )
2299                },
2300                Statement {
2301                    variable: Variable { id: 1, bounds: (0,1) },
2302                    expression: Some(
2303                        AtLeast {
2304                            ids: vec![3,4],
2305                            bias: 1,
2306                            sign: Sign::Negative,
2307                        }
2308                    )
2309                },
2310                Statement {
2311                    variable: Variable { id: 2, bounds: (0,1) },
2312                    expression: Some(
2313                        AtLeast {
2314                            ids: vec![5,6,7],
2315                            bias: -3,
2316                            sign: Sign::Positive,
2317                        }
2318                    )
2319                },
2320                Statement {
2321                    variable: Variable { id: 3, bounds: (0,1) },
2322                    expression: None
2323                },
2324                Statement {
2325                    variable: Variable { id: 4, bounds: (0,1) },
2326                    expression: None
2327                },
2328                Statement {
2329                    variable: Variable { id: 5, bounds: (0,1) },
2330                    expression: None
2331                },
2332                Statement {
2333                    variable: Variable { id: 6, bounds: (0,1) },
2334                    expression: None
2335                },
2336                Statement {
2337                    variable: Variable { id: 7, bounds: (0,1) },
2338                    expression: None
2339                },
2340            ]
2341        };
2342        let actual: Vec<GeLineq> = t.to_lineqs(false, false);
2343        let expected: Vec<GeLineq> = vec![
2344            GeLineq {
2345                id: None,
2346                bias: 0,
2347                bounds: vec![(0,1),(0,1),(0,1)],
2348                coeffs: vec![-1,1,1],
2349                indices: vec![0,1,2]
2350            },
2351            GeLineq {
2352                id: None,
2353                bias: 0,
2354                bounds: vec![(0,1),(0,1),(0,1),(0,1)],
2355                coeffs: vec![-3,1,1,1],
2356                indices: vec![2,5,6,7]
2357            },
2358            GeLineq {
2359                id: None,
2360                bias: 2,
2361                bounds: vec![(0,1),(0,1),(0,1)],
2362                coeffs: vec![-1,-1,-1],
2363                indices: vec![1,3,4]
2364            },
2365        ];
2366        assert!(validate_theory_lineqs(t.clone(), actual, expected));
2367        let actual: Vec<GeLineq> = t.to_lineqs(true, false);
2368        let expected: Vec<GeLineq> = vec![
2369            GeLineq {
2370                id: None,
2371                bias: -1,
2372                bounds: vec![(0,1),(0,1)],
2373                coeffs: vec![1,1],
2374                indices: vec![1,2]
2375            },
2376            GeLineq {
2377                id: None,
2378                bias: 0,
2379                bounds: vec![(0,1),(0,1),(0,1),(0,1)],
2380                coeffs: vec![-3,1,1,1],
2381                indices: vec![2,5,6,7]
2382            },
2383            GeLineq {
2384                id: None,
2385                bias: 2,
2386                bounds: vec![(0,1),(0,1),(0,1)],
2387                coeffs: vec![-1,-1,-1],
2388                indices: vec![1,3,4]
2389            },
2390        ];
2391        assert!(validate_theory_lineqs(t.clone(), actual, expected));
2392        let actual: Vec<GeLineq> = t.to_lineqs(false, true); // reduce overrides active
2393        let expected: Vec<GeLineq> = vec![
2394            GeLineq {
2395                id: None,
2396                bias: 3,
2397                bounds: vec![(0,1),(0,1),(0,1),(0,1),(0,1)],
2398                coeffs: vec![-3,-3,1,1,1],
2399                indices: vec![3,4,5,6,7]
2400            },
2401        ];
2402        assert!(validate_theory_lineqs(t.clone(), actual, expected));
2403        let actual: Vec<GeLineq> = t.to_lineqs(true, true); // same as previous
2404        let expected: Vec<GeLineq> = vec![
2405            GeLineq {
2406                id: None,
2407                bias: 3,
2408                bounds: vec![(0,1),(0,1),(0,1),(0,1),(0,1)],
2409                coeffs: vec![-3,-3,1,1,1],
2410                indices: vec![3,4,5,6,7]
2411            },
2412        ];
2413        assert!(validate_theory_lineqs(t.clone(), actual, expected));
2414    }
2415    #[test]
2416    fn test_solver(){
2417        let obj = vec![3.0, 1.0, -1.0, -1.0, 1.0];
2418        let ilp = solver::IntegerLinearProgram {
2419            ge_ph: Polyhedron {
2420                a: linalg::Matrix { 
2421                    val: vec![-3.0, -3.0, 1.0, 1.0, 1.0], 
2422                    ncols: 5, 
2423                    nrows: 1 
2424                },
2425                b: vec![-3.0],
2426                variables: vec![
2427                    VariableFloat {id: 3, bounds: (0.0, 1.0) },
2428                    VariableFloat {id: 4, bounds: (0.0, 1.0) },
2429                    VariableFloat {id: 5, bounds: (0.0, 1.0) },
2430                    VariableFloat {id: 6, bounds: (0.0, 1.0) },
2431                    VariableFloat {id: 7, bounds: (0.0, 1.0) }
2432                ],
2433                index: (0..1).map(|x| Some(x as u32)).collect(),
2434            },
2435            eq_ph: Default::default(),
2436            of: obj.to_vec(),
2437        };
2438        let actual_solution = ilp.solve();
2439        let expected_solution_x = vec![1,0,0,0,1];
2440        assert_eq!(actual_solution.x, expected_solution_x);
2441        assert_eq!(actual_solution.z, 4);
2442    }
2443
2444    #[test]
2445    fn test_theory_to_ge_polyhedron() {
2446
2447        fn validate(actual: Polyhedron, expected: Polyhedron) -> bool {
2448            if actual.variables != expected.variables {
2449                return false;
2450            }
2451
2452            let variable_bounds: Vec<(f64, f64)> = actual.bounds();
2453            let base : i64 = 2;
2454            let max_combinations: i64 = base.pow(15);
2455            let bound_ranges: Vec<std::ops::Range<i64>> = variable_bounds.iter().map(|x| Range {start: (x.0 as i64), end: (x.1 as i64)+1}).collect_vec();
2456            let n_combinations : i64 = variable_bounds.iter().map(|x| ((x.1 as i64)+1)-(x.0 as i64)).product();
2457            if n_combinations > max_combinations {
2458                panic!("number of combinations ({n_combinations}) to test are more than allowed ({max_combinations})");
2459            }
2460
2461            let combinations: Vec<Vec<i64>> = bound_ranges.clone().into_iter().multi_cartesian_product().collect();
2462            let combination_matrix: Matrix = Matrix {
2463                val: combinations.clone().into_iter().concat().iter().map(|x| (*x) as f64).collect(),
2464                ncols: bound_ranges.len(),
2465                nrows: combinations.len(),
2466            };
2467
2468            let actual_dot: Matrix = actual.a.dot(&combination_matrix.transpose());
2469            let actual_dot_cmp: Vec<bool> = actual_dot.val.chunks(actual_dot.nrows).map(|x| {
2470                x.iter().zip(actual.b.clone()).map(|(v0,v1)| (*v0) >= v1).collect()
2471            }).concat();
2472            let expected_dot: Matrix = expected.a.dot(&combination_matrix.transpose());
2473            let expected_dot_cmp: Vec<bool> = expected_dot.val.chunks(expected_dot.nrows).map(|x| {
2474                x.iter().zip(expected.b.clone()).map(|(v0,v1)| (*v0) >= v1).collect()
2475            }).concat();
2476
2477            return actual_dot_cmp == expected_dot_cmp;
2478        }
2479
2480        // Depth 3
2481        // 0: 1 & 2
2482        // 1: 3 + 4 >= 6 (3 has bounds (-5,5), 4 is bool) 
2483        // 2: 5 + 6 >= 4 (5 has bounds (-3,3), 6 is bool) 
2484
2485        // Since each variable must be set to their max we can
2486        // reduce into one single constraint (3)+(4)+(5)+(6) >= 10
2487
2488        // Expected constraints:
2489        let t = Theory {
2490            id: String::from("A"),
2491            statements: vec![
2492                Statement {
2493                    variable: Variable { id: 0, bounds: (0,1) },
2494                    expression: Some(
2495                        AtLeast {
2496                            ids: vec![1,2],
2497                            bias: -2,
2498                            sign: Sign::Positive,
2499                        }
2500                    )
2501                },
2502                Statement {
2503                    variable: Variable { id: 1, bounds: (0,1) },
2504                    expression: Some(
2505                        AtLeast {
2506                            ids: vec![3,4],
2507                            bias: -6,
2508                            sign: Sign::Positive,
2509                        }
2510                    )
2511                },
2512                Statement {
2513                    variable: Variable { id: 2, bounds: (0,1) },
2514                    expression: Some(
2515                        AtLeast {
2516                            ids: vec![5,6],
2517                            bias: -4,
2518                            sign: Sign::Positive,
2519                        }
2520                    )
2521                },
2522                Statement {
2523                    variable: Variable { id: 3, bounds: (-5,5) },
2524                    expression: None
2525                },
2526                Statement {
2527                    variable: Variable { id: 4, bounds: (0,1) },
2528                    expression: None
2529                },
2530                Statement {
2531                    variable: Variable { id: 5, bounds: (-3,3) },
2532                    expression: None
2533                },
2534                Statement {
2535                    variable: Variable { id: 6, bounds: (0,1) },
2536                    expression: None
2537                },
2538            ]
2539        };
2540        
2541        let expected: Polyhedron = Polyhedron { 
2542            a: linalg::Matrix { 
2543                val: vec![1.0, 1.0, 1.0, 1.0], 
2544                ncols: 4, 
2545                nrows: 1 
2546            },
2547            b: vec![10.0],
2548            variables: vec![
2549                VariableFloat {
2550                    id: 3,
2551                    bounds: (-5.0, 5.0),
2552                },
2553                VariableFloat {
2554                    id: 4,
2555                    bounds: (0.0, 1.0),
2556                },
2557                VariableFloat {
2558                    id: 5,
2559                    bounds: (-3.0, 3.0),
2560                },
2561                VariableFloat {
2562                    id: 6,
2563                    bounds: (0.0, 1.0),
2564                },
2565            ],
2566            index: (0..1).map(|x| Some(x as u32)).collect(),
2567        };
2568        assert!(validate(t.to_ge_polyhedron(true, true).to_dense_polyhedron(), expected));
2569
2570        // Depth 4
2571        // 0: 1 & 2 & 3
2572        // 1: 4 | 5
2573        // 2: 5 | 6
2574        // 3: 7 & 8
2575        // 4: 9 & 10
2576        // 5: -11
2577        // 6: 12 | 13
2578
2579        // We test two things:
2580        // 1) sharing variable works (both 1 and 2 has 5 as child and is reducable)
2581        // 2) direct reducement works (0 and 3 has same constraint, therefore 0 should have 7 and 8 directly as children)  <- NOT IMPLEMENTED
2582
2583        // Expected constraints:
2584        //   ( 1) +( 2) +( 3) -3
2585        // -2( 3) +( 7) +( 8) +0
2586        // -1(11) +(12) +(13) +0 (instead of 5 & 6)
2587        //   ( 9) +(10)-2(11) +0 (instead of 4 & 5)
2588
2589        let t = Theory {
2590            id: String::from("A"),
2591            statements: vec![
2592                Statement {
2593                    variable: Variable { id: 0, bounds: (0,1) },
2594                    expression: Some(
2595                        AtLeast {
2596                            ids: vec![1,2,3],
2597                            bias: -3,
2598                            sign: Sign::Positive,
2599                        }
2600                    )
2601                },
2602                Statement {
2603                    variable: Variable { id: 1, bounds: (0,1) },
2604                    expression: Some(
2605                        AtLeast {
2606                            ids: vec![4,5],
2607                            bias: -1,
2608                            sign: Sign::Positive,
2609                        }
2610                    )
2611                },
2612                Statement {
2613                    variable: Variable { id: 2, bounds: (0,1) },
2614                    expression: Some(
2615                        AtLeast {
2616                            ids: vec![5,6],
2617                            bias: -1,
2618                            sign: Sign::Positive,
2619                        }
2620                    )
2621                },
2622                Statement {
2623                    variable: Variable { id: 3, bounds: (0,1) },
2624                    expression: Some(
2625                        AtLeast {
2626                            ids: vec![7,8],
2627                            bias: -2,
2628                            sign: Sign::Positive,
2629                        }
2630                    )
2631                },
2632                Statement {
2633                    variable: Variable { id: 4, bounds: (0,1) },
2634                    expression: Some(
2635                        AtLeast {
2636                            ids: vec![9,10],
2637                            bias: -2,
2638                            sign: Sign::Positive,
2639                        }
2640                    )
2641                },
2642                Statement {
2643                    variable: Variable { id: 5, bounds: (0,1) },
2644                    expression: Some(
2645                        AtLeast {
2646                            ids: vec![11],
2647                            bias: 0,
2648                            sign: Sign::Negative,
2649                        }
2650                    )
2651                },
2652                Statement {
2653                    variable: Variable { id: 6, bounds: (0,1) },
2654                    expression: Some(
2655                        AtLeast {
2656                            ids: vec![12,13],
2657                            bias: -1,
2658                            sign: Sign::Positive,
2659                        }
2660                    )
2661                },
2662                Statement {
2663                    variable: Variable { id: 7, bounds: (0,1) },
2664                    expression: None
2665                },
2666                Statement {
2667                    variable: Variable { id: 8, bounds: (0,1) },
2668                    expression: None
2669                },
2670                Statement {
2671                    variable: Variable { id: 9, bounds: (0,1) },
2672                    expression: None
2673                },
2674                Statement {
2675                    variable: Variable { id: 10, bounds: (0,1) },
2676                    expression: None
2677                },
2678                Statement {
2679                    variable: Variable { id: 11, bounds: (0,1) },
2680                    expression: None
2681                },
2682                Statement {
2683                    variable: Variable { id: 12, bounds: (0,1) },
2684                    expression: None
2685                },
2686                Statement {
2687                    variable: Variable { id: 13, bounds: (0,1) },
2688                    expression: None
2689                },
2690            ]
2691        };
2692
2693        let expected: Polyhedron = Polyhedron { 
2694            a: linalg::Matrix { 
2695                val: vec![1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, -2.0, 0.0, 0.0], 
2696                ncols: 10, 
2697                nrows: 4
2698            },
2699            b: vec![3.0, 0.0, 0.0, 0.0],
2700            variables: vec![
2701                VariableFloat {id: 1, bounds: (0.0, 1.0)},
2702                VariableFloat {id: 2, bounds: (0.0, 1.0)},
2703                VariableFloat {id: 3, bounds: (0.0, 1.0)},
2704                VariableFloat {id: 7, bounds: (0.0, 1.0)},
2705                VariableFloat {id: 8, bounds: (0.0, 1.0)},
2706                VariableFloat {id: 9, bounds: (0.0, 1.0)},
2707                VariableFloat {id: 10, bounds: (0.0, 1.0)},
2708                VariableFloat {id: 11, bounds: (0.0, 1.0)},
2709                VariableFloat {id: 12, bounds: (0.0, 1.0)},
2710                VariableFloat {id: 13, bounds: (0.0, 1.0)},
2711            ],
2712            index: (0..4).map(|x| Some(x as u32)).collect(),
2713            // variable_ids: vec![1, 2, 3, 7, 8, 9, 10, 11, 12, 13] 
2714        };
2715        assert!(validate(t.to_ge_polyhedron(true, true).to_dense_polyhedron(), expected));
2716
2717        // Depth 3
2718        // 0: 1 -> 2
2719        // 1: 3 & 4
2720        // 2: 5 & 6 & 7
2721        // Expects to be reduced into 1 constraint
2722        // -3(3)-3(4)+(5)+(6)+(7)-3 >= 0 
2723
2724        let t = Theory {
2725            id: String::from("A"),
2726            statements: vec![
2727                Statement {
2728                    variable: Variable { id: 0, bounds: (0,1) },
2729                    expression: Some(
2730                        AtLeast {
2731                            ids: vec![1,2],
2732                            bias: -1,
2733                            sign: Sign::Positive,
2734                        }
2735                    )
2736                },
2737                Statement {
2738                    variable: Variable { id: 1, bounds: (0,1) },
2739                    expression: Some(
2740                        AtLeast {
2741                            ids: vec![3,4],
2742                            bias: 1,
2743                            sign: Sign::Negative,
2744                        }
2745                    )
2746                },
2747                Statement {
2748                    variable: Variable { id: 2, bounds: (0,1) },
2749                    expression: Some(
2750                        AtLeast {
2751                            ids: vec![5,6,7],
2752                            bias: -3,
2753                            sign: Sign::Positive,
2754                        }
2755                    )
2756                },
2757                Statement {
2758                    variable: Variable { id: 3, bounds: (0,1) },
2759                    expression: None
2760                },
2761                Statement {
2762                    variable: Variable { id: 4, bounds: (0,1) },
2763                    expression: None
2764                },
2765                Statement {
2766                    variable: Variable { id: 5, bounds: (0,1) },
2767                    expression: None
2768                },
2769                Statement {
2770                    variable: Variable { id: 6, bounds: (0,1) },
2771                    expression: None
2772                },
2773                Statement {
2774                    variable: Variable { id: 7, bounds: (0,1) },
2775                    expression: None
2776                },
2777            ]
2778        };
2779
2780        let expected: Polyhedron = Polyhedron { 
2781            a: linalg::Matrix { 
2782                val: vec![-3.0, -3.0, 1.0, 1.0, 1.0], 
2783                ncols: 5, 
2784                nrows: 1
2785            },
2786            b: vec![-3.0],
2787            variables: vec![
2788                VariableFloat {id: 3, bounds: (0.0, 1.0)},
2789                VariableFloat {id: 4, bounds: (0.0, 1.0)},
2790                VariableFloat {id: 5, bounds: (0.0, 1.0)},
2791                VariableFloat {id: 6, bounds: (0.0, 1.0)},
2792                VariableFloat {id: 7, bounds: (0.0, 1.0)},
2793            ],
2794            index: (0..1).map(|x| Some(x as u32)).collect(),
2795        };
2796        assert!(validate(t.to_ge_polyhedron(true, true).to_dense_polyhedron(), expected));
2797    }
2798
2799    #[test]
2800    fn test_theory_solve() {
2801        let t = Theory {
2802            id: String::from("A"),
2803            statements: vec![
2804                Statement {
2805                    variable: Variable { id: 0, bounds: (0,1) },
2806                    expression: Some(
2807                        AtLeast {
2808                            ids: vec![1,2],
2809                            bias: -1,
2810                            sign: Sign::Positive,
2811                        }
2812                    )
2813                },
2814                Statement {
2815                    variable: Variable { id: 1, bounds: (0,1) },
2816                    expression: Some(
2817                        AtLeast {
2818                            ids: vec![3,4],
2819                            bias: 1,
2820                            sign: Sign::Negative,
2821                        }
2822                    )
2823                },
2824                Statement {
2825                    variable: Variable { id: 2, bounds: (0,1) },
2826                    expression: Some(
2827                        AtLeast {
2828                            ids: vec![5,6,7],
2829                            bias: -3,
2830                            sign: Sign::Positive,
2831                        }
2832                    )
2833                },
2834                Statement {
2835                    variable: Variable { id: 3, bounds: (0,1) },
2836                    expression: None
2837                },
2838                Statement {
2839                    variable: Variable { id: 4, bounds: (0,1) },
2840                    expression: None
2841                },
2842                Statement {
2843                    variable: Variable { id: 5, bounds: (0,1) },
2844                    expression: None
2845                },
2846                Statement {
2847                    variable: Variable { id: 6, bounds: (0,1) },
2848                    expression: None
2849                },
2850                Statement {
2851                    variable: Variable { id: 7, bounds: (0,1) },
2852                    expression: None
2853                },
2854            ]
2855        };
2856        let actual_solutions = t.solve(
2857            vec![
2858                vec![(3, 1.0), (4, 1.0)].iter().cloned().collect(),
2859                vec![(3, 2.0), (4, 1.0), (5, -1.0), (6, -1.0), (7, -1.0)].iter().cloned().collect(),
2860                // vec![3.0, 1.0,-1.0,-1.0, 1.0],
2861            ], true);
2862        let expected_solutions = vec![
2863            vec![(3,1),(4,1),(5,1),(6,1),(7,1)].iter().cloned().collect(),
2864            vec![(3,1),(4,0),(5,0),(6,0),(7,0)].iter().cloned().collect(),
2865        ];
2866        assert!(actual_solutions.iter().zip(expected_solutions).all(|(x,y)| x.0 == y));
2867    }
2868}