1use std::collections::HashSet;
2use std::collections::{HashMap, VecDeque, hash_map::DefaultHasher};
3use std::hash::{Hash, Hasher};
4use std::ops::{Index, RangeInclusive};
5use itertools::Itertools;
6use indexmap::{IndexMap, IndexSet};
7
8pub type Bound = (i64, i64);
11
12pub type ID = String;
14
15fn create_hash(data: &Vec<(String, i64)>, num: i64) -> u64 {
27    let mut hasher = DefaultHasher::new();
29    
30    for (s, i) in data {
32        s.hash(&mut hasher);
33        i.hash(&mut hasher);
34    }
35    
36    num.hash(&mut hasher);
38    
39    hasher.finish()
41}
42
43fn bound_fixed(b: Bound) -> bool {
51    b.0 == b.1
52}
53
54fn bound_bool(b: Bound) -> bool {
62    b.0 == 0 && b.1 == 1
63}
64
65fn bound_add(b1: Bound, b2: Bound) -> Bound {
74    return (b1.0 + b2.0, b1.1 + b2.1);
75}
76
77fn bound_multiply(k: i64, b: Bound) -> Bound {
89    if k < 0 {
90        return (k*b.1, k*b.0);
91    } else {
92        return (k*b.0, k*b.1);
93    }
94}
95
96fn bound_span(b: Bound) -> i64 {
104    return (b.1 - b.0).abs();
105}
106
107pub struct SparseIntegerMatrix {
112    pub rows: Vec<usize>,
114    pub cols: Vec<usize>,
116    pub vals: Vec<i64>,
118    pub shape: (usize, usize),
120}
121
122pub struct DenseIntegerMatrix {
126    pub data: Vec<Vec<i64>>,
128    pub shape: (usize, usize),
130}
131
132impl DenseIntegerMatrix {
133    pub fn new(rows: usize, cols: usize) -> DenseIntegerMatrix {
142        DenseIntegerMatrix {
143            data: vec![vec![0; cols]; rows],
144            shape: (rows, cols),
145        }
146    }
147
148    pub fn dot_product(&self, vector: &Vec<i64>) -> Vec<i64> {
162        let mut result = vec![0; self.shape.0];
163        for i in 0..self.shape.0 {
164            for j in 0..self.shape.1 {
165                result[i] += self.data[i][j] * vector[j];
166            }
167        }
168        result
169    }
170}
171
172pub struct DensePolyhedron {
179    pub A: DenseIntegerMatrix,
181    pub b: Vec<i64>,
183    pub columns: Vec<String>,
185    pub integer_columns: Vec<String>,
187}
188
189impl DensePolyhedron {
190    pub fn to_vector(&self, from_assignments: &HashMap<String, i64>) -> Vec<i64> {
199        let mut vector: Vec<i64> = vec![0; self.columns.len()];
200        for (index, v) in from_assignments
201            .iter()
202            .filter_map(|(k, v)| self.columns.iter().position(|col| col == k).map(|index| (index, v)))
203        {
204            vector[index] = *v;
205        }
206        vector
207    }
208
209    pub fn assume(&self, values: &HashMap<String, i64>) -> DensePolyhedron {
220        let mut new_A_data   = self.A.data.clone();     let mut new_b        = self.b.clone();          let mut new_columns  = self.columns.clone();    let mut new_int_cols = self.integer_columns.clone();  let mut to_remove: Vec<(usize, String, i64)> = values
229            .iter()
230            .filter_map(|(name, &val)| {
231                self.columns
233                    .iter()
234                    .position(|col| col == name)
235                    .map(|idx| (idx, name.clone(), val))
236            })
237            .collect();
238
239        to_remove.sort_by(|a, b| b.0.cmp(&a.0));
242
243        for (col_idx, col_name, fixed_val) in to_remove {
248            for row in 0..new_A_data.len() {
249                new_b[row] -= new_A_data[row][col_idx] * fixed_val;
251                new_A_data[row].remove(col_idx);
253            }
254            new_columns.remove(col_idx);
256            new_int_cols.retain(|c| c != &col_name);
258        }
259
260        let new_shape = (new_A_data.len(), new_columns.len());
262        let new_A = DenseIntegerMatrix {
263            data: new_A_data,
264            shape: new_shape,
265        };
266
267        DensePolyhedron {
269            A: new_A,
270            b: new_b,
271            columns: new_columns,
272            integer_columns: new_int_cols,
273        }
274    }
275
276    pub fn evaluate(&self, assignments: &IndexMap<String, Bound>) -> Bound {
289        let mut lower_bounds = HashMap::new();
290        let mut upper_bounds = HashMap::new();
291        for (key, bound) in assignments {
292            lower_bounds.insert(key.clone(), bound.0);
293            upper_bounds.insert(key.clone(), bound.1);
294        }
295
296        let lower_result = self.A.dot_product(&self.to_vector(&lower_bounds))
297            .iter()
298            .zip(&self.b)
299            .all(|(a, b)| a >= b);
300
301        let upper_result = self.A.dot_product(&self.to_vector(&upper_bounds))
302            .iter()
303            .zip(&self.b)
304            .all(|(a, b)| a >= b);
305
306        (lower_result as i64, upper_result as i64)
307    }
308}
309
310impl From<SparseIntegerMatrix> for DenseIntegerMatrix {
311    fn from(sparse: SparseIntegerMatrix) -> DenseIntegerMatrix {
312        let mut dense = DenseIntegerMatrix::new(sparse.shape.0, sparse.shape.1);
313        for ((&row, &col), &val) in sparse.rows.iter().zip(&sparse.cols).zip(&sparse.vals) {
314            dense.data[row][col] = val;
315        }
316        dense
317    }
318}
319
320impl From<DenseIntegerMatrix> for SparseIntegerMatrix {
321    fn from(dense: DenseIntegerMatrix) -> SparseIntegerMatrix {
322        let mut rows = Vec::new();
323        let mut cols = Vec::new();
324        let mut vals = Vec::new();
325        for (i, row) in dense.data.iter().enumerate() {
326            for (j, &val) in row.iter().enumerate() {
327                if val != 0 {
328                    rows.push(i);
329                    cols.push(j);
330                    vals.push(val);
331                }
332            }
333        }
334        SparseIntegerMatrix {
335            rows,
336            cols,
337            vals,
338            shape: dense.shape,
339        }
340    }
341}
342
343impl SparseIntegerMatrix {
344    pub fn new() -> SparseIntegerMatrix {
349        SparseIntegerMatrix {
350            rows: Vec::new(),
351            cols: Vec::new(),
352            vals: Vec::new(),
353            shape: (0, 0),
354        }
355    }
356}
357
358pub struct SparsePolyhedron {
362    pub A: SparseIntegerMatrix,
364    pub b: Vec<i64>,
366    pub columns: Vec<String>,
368    pub integer_columns: Vec<String>,
370}
371
372impl From<SparsePolyhedron> for DensePolyhedron {
373    fn from(sparse: SparsePolyhedron) -> DensePolyhedron {
374        let mut dense_matrix = DenseIntegerMatrix::new(sparse.A.shape.0, sparse.A.shape.1);
375        for ((&row, &col), &val) in sparse.A.rows.iter().zip(&sparse.A.cols).zip(&sparse.A.vals) {
376            dense_matrix.data[row][col] = val;
377        }
378        DensePolyhedron {
379            A: dense_matrix,
380            b: sparse.b,
381            columns: sparse.columns,
382            integer_columns: sparse.integer_columns,
383        }
384    }
385}
386
387impl From<DensePolyhedron> for SparsePolyhedron {
388    fn from(dense: DensePolyhedron) -> SparsePolyhedron {
389        let mut rows = Vec::new();
390        let mut cols = Vec::new();
391        let mut vals = Vec::new();
392        for (i, row) in dense.A.data.iter().enumerate() {
393            for (j, &val) in row.iter().enumerate() {
394                if val != 0 {
395                    rows.push(i);
396                    cols.push(j);
397                    vals.push(val);
398                }
399            }
400        }
401        SparsePolyhedron {
402            A: SparseIntegerMatrix {
403                rows,
404                cols,
405                vals,
406                shape: dense.A.shape,
407            },
408            b: dense.b,
409            columns: dense.columns,
410            integer_columns: dense.integer_columns,
411        }
412    }
413}
414
415pub type Coefficient = (String, i64);
417
418pub type Assignment = IndexMap<ID, Bound>;
420
421pub type VBound = (f64, f64);
423
424pub type MultiBound = (Bound, VBound);
426
427pub type ValuedAssignment = IndexMap<ID, MultiBound>;
429
430pub struct Constraint {
432    pub coefficients: Vec<Coefficient>,
434    pub bias: Bound,
436}
437
438impl Constraint {
439    pub fn dot(&self, values: &IndexMap<String, Bound>) -> Bound {
450        self.coefficients.iter().fold((0, 0), |acc, (key, coeff)| {
451            let bound = values.get(key).unwrap_or(&(0, 0));
452            let (min, max) = bound_multiply(*coeff, *bound);
453            (acc.0 + min, acc.1 + max)
454        })
455    }
456
457    pub fn evaluate(&self, values: &IndexMap<String, Bound>) -> Bound {
470        let bound = self.dot(values);
471        return (
472            (bound.0 + self.bias.0 >= 0) as i64,
473            (bound.1 + self.bias.1 >= 0) as i64
474        )
475    }
476
477    pub fn negate(&self) -> Constraint {
485        Constraint {
486            coefficients: self.coefficients.iter().map(|(key, val)| {
487                (key.clone(), -val)
488            }).collect(),
489            bias: (
490                -self.bias.0-1,
491                 -self.bias.1-1
492            ),
493        }
494    }
495}
496pub enum BoolExpression {
498    Composite(Constraint),
500    Primitive(Bound),
502}
503
504pub struct Node {
506    pub expression: BoolExpression,
508    pub coefficient: f64,
510}
511
512pub struct Pldag {
521    pub nodes: IndexMap<String, Node>,
523}
524
525impl Pldag {
526    pub fn new() -> Pldag {
531        Pldag {
532            nodes: IndexMap::new(),
533        }
534    }
535
536    pub fn transitive_dependencies(&self) -> HashMap<ID, HashSet<ID>> {
544        let mut memo: HashMap<String, HashSet<String>> = HashMap::new();
546        let mut result: HashMap<String, HashSet<String>> = HashMap::new();
547
548        for key in self.nodes.keys() {
549            let deps = self._collect_deps(key, &mut memo);
551            result.insert(key.clone(), deps);
552        }
553
554        result
555    }
556
557    fn _collect_deps(&self, node: &ID, memo: &mut HashMap<ID, HashSet<ID>>) -> HashSet<ID> {
566        if let Some(cached) = memo.get(node) {
568            return cached.clone();
569        }
570
571        let mut deps = HashSet::new();
572
573        if let Some(node_data) = self.nodes.get(node) {
574            if let BoolExpression::Composite(constraint) = &node_data.expression {
575                for (child_id, _) in &constraint.coefficients {
576                    deps.insert(child_id.clone());
578                    let sub = self._collect_deps(child_id, memo);
580                    deps.extend(sub);
581                }
582            }
583            }
585
586        memo.insert(node.clone(), deps.clone());
588        deps
589    }
590
591    pub fn primitive_combinations(&self) -> impl Iterator<Item = HashMap<ID, i64>> {
599        let primitives: Vec<(String, (i64, i64))> = self.nodes
601            .iter()
602            .filter_map(|(key, node)| {
603                if let BoolExpression::Primitive(bound) = &node.expression {
604                    Some((key.clone(), *bound))
605                } else {
606                    None
607                }
608            })
609            .collect();
610
611        let keys: Vec<String> = primitives.iter().map(|(k, _)| k.clone()).collect();
613
614        let ranges: Vec<RangeInclusive<i64>> = primitives
616            .iter()
617            .map(|(_, (low, high))| *low..=*high)
618            .collect();
619
620        ranges
622            .into_iter()
623            .map(|r| r.collect::<Vec<_>>())
624            .multi_cartesian_product()
625            .map(move |values| {
627                keys.iter()
628                    .cloned()
629                    .zip(values.into_iter())
630                    .collect::<HashMap<String, i64>>()
631            })
632    }
633
634    pub fn propagate(&self, assignment: &Assignment) -> Assignment {
645
646        let mut result= assignment.clone();
647
648        for (key, node) in self.nodes.iter() {
650            if !result.contains_key(key) {
651                if let BoolExpression::Primitive(bound) = &node.expression {
652                    result.insert(key.clone(), *bound);
653                }
654            }
655        }
656
657        let mut S: VecDeque<String> = self.nodes
661            .iter()
662            .filter(|(key, node)| {
663                match &node.expression {
664                    BoolExpression::Composite(composite) => {
665                    composite.coefficients.iter().all(|x| {
666                        match self.nodes.get(&x.0) {
667                        Some(node_data) => matches!(node_data.expression, BoolExpression::Primitive(_)),
668                        _ => false
669                        }
670                    }) && !result.contains_key(&key.to_string())
671                    },
672                    BoolExpression::Primitive(_) => false,
673                }
674            })
675            .map(|(key, _)| key.clone())
676            .collect();
677
678        let mut visited = HashSet::new();
682
683        while let Some(s) = S.pop_front() {
685
686            if visited.contains(&s) {
688                panic!("Cycle detected in the graph");
689            }
690
691            visited.insert(s.clone());
693            
694            match self.nodes.get(&s) {
697                Some(node_data) => {
698                    if let BoolExpression::Composite(composite) = &node_data.expression {
699                        result.insert(
700                            s.clone(), 
701                            composite.evaluate(&result)
702                        );
703            
704                        let incoming = self.nodes
707                            .iter()
708                            .filter(|(key, node)| {
709                                !result.contains_key(&key.to_string()) && match &node.expression {
710                                    BoolExpression::Composite(sub_composite) => {
711                                        sub_composite.coefficients.iter().any(|x| x.0 == s)
712                                    },
713                                    _ => false
714                                }
715                            })
716                            .map(|(key, _)| key.clone())
717                            .collect::<Vec<String>>();
718            
719                        for incoming_id in incoming {
721                            if !S.contains(&incoming_id) {
722                                S.push_back(incoming_id);
723                            }
724                        }
725                    }
726                },
727                _ => {}
728            }
729        }
730
731        return result;
732    }
733    
734    pub fn propagate_default(&self) -> Assignment {
742        let assignments: IndexMap<String, Bound> = self.nodes.iter().filter_map(|(key, node)| {
743            if let BoolExpression::Primitive(bound) = &node.expression {
744                Some((key.clone(), *bound))
745            } else {
746                None
747            }
748        }).collect();
749        self.propagate(&assignments)
750    }
751
752    pub fn propagate_many_coefs(&self, assignments: Vec<&Assignment>) -> Vec<ValuedAssignment> {
765        let transitive_deps = self.transitive_dependencies();
767
768        let mut assignment_results: Vec<ValuedAssignment> = Vec::new();
769        
770        for assignment in assignments {
771
772            let result = self.propagate(assignment);
774    
775            let mut valued_assigment: ValuedAssignment = IndexMap::new();
776            for (key, deps) in transitive_deps.iter() {
778                let mut coef_sum: VBound = (0.0, 0.0);
779                if let Some(node) = self.nodes.get(key) {
780                    coef_sum = (node.coefficient, node.coefficient);
781                }
782                for dep in deps {
783                    let dep_res = result.get(dep).unwrap_or(&(0, 1));
784                    if let Some(node) = self.nodes.get(dep) {
785                        if node.coefficient < 0.0 {
786                            coef_sum.0 += node.coefficient * dep_res.1 as f64;
787                            coef_sum.1 += node.coefficient * dep_res.0 as f64;
788                        } else {
789                            coef_sum.0 += node.coefficient * dep_res.0 as f64;
790                            coef_sum.1 += node.coefficient * dep_res.1 as f64;
791                        }
792                    }
793                }
794                if let Some(bound) = result.get(key) {
795                    valued_assigment.insert(key.clone(), (*bound, coef_sum));
796                }
797            }
798    
799            assignment_results.push(valued_assigment);
801        }
802
803        return assignment_results;
804
805    }
806
807    pub fn propagate_coefs(&self, assignment: &Assignment) -> ValuedAssignment {
820        return self.propagate_many_coefs(vec![assignment]).into_iter().next()
822            .unwrap_or_else(|| panic!("No assignments found after propagation with {:?}", assignment));
823    }
824
825    pub fn propagate_coefs_default(&self) -> ValuedAssignment {
833        let assignments: IndexMap<String, Bound> = self.nodes.iter().filter_map(|(key, node)| {
835            if let BoolExpression::Primitive(bound) = &node.expression {
836                Some((key.clone(), *bound))
837            } else {
838                None
839            }
840        }).collect();
841        self.propagate_coefs(&assignments)
842    }
843    
844    pub fn get_objective(&self) -> IndexMap<String, f64> {
853        self.nodes.iter().filter_map(|(key, node)| {
855            if let BoolExpression::Primitive(_) = &node.expression {
856                Some((key.clone(), node.coefficient))
857            } else {
858                None
859            }
860        }).collect()
861    }
862    
863    pub fn to_sparse_polyhedron(&self, double_binding: bool, integer_constraints: bool, fixed_constraints: bool) -> SparsePolyhedron {
876
877        fn get_coef_bounds(composite: &Constraint, nodes: &IndexMap<String, Node>) -> IndexMap<String, Bound> {
878            let mut coef_bounds: IndexMap<String, Bound> = IndexMap::new();
879            for (coef_key, _) in composite.coefficients.iter() {
880                let coef_node = nodes.get(&coef_key.to_string())
881                    .unwrap_or_else(|| panic!("Coefficient key '{}' not found in nodes", coef_key));
882                match &coef_node.expression {
883                    BoolExpression::Primitive(bound) => {
884                        coef_bounds.insert(coef_key.to_string(), *bound);
885                    },
886                    _ => {coef_bounds.insert(coef_key.to_string(), (0,1));}
887                }
888            }
889            return coef_bounds;
890        }
891
892        let mut A_matrix = SparseIntegerMatrix::new();
894        let mut b_vector: Vec<i64> = Vec::new();
895
896        let primitives: HashMap<&String, Bound> = self.nodes.iter()
898            .filter_map(|(key, node)| {
899                if let BoolExpression::Primitive(bound) = &node.expression {
900                    Some((key, *bound))
901                } else {
902                    None
903                }
904            })
905            .collect();
906
907        let composites: HashMap<&String, &Constraint> = self.nodes.iter()
909            .filter_map(|(key, node)| {
910                if let BoolExpression::Composite(constraint) = &node.expression {
911                    Some((key, constraint))
912                } else {
913                    None
914                }
915            })
916            .collect();
917
918        let column_names_map: IndexMap<String, usize> = primitives.keys().chain(composites.keys()).enumerate().map(|(i, key)| (key.to_string(), i)).collect();
920
921        let mut row_i: usize = 0;
923
924        for (key, composite) in composites {
925
926            let ki = *column_names_map.get(key).unwrap();
928
929            let coef_bounds = get_coef_bounds(composite, &self.nodes);
931
932            let ib_phi = composite.dot(&coef_bounds);
940
941            let d_pi = std::cmp::max(ib_phi.0.abs(), ib_phi.1.abs());
943            
944            A_matrix.rows.push(row_i);
946            A_matrix.cols.push(ki);
947            A_matrix.vals.push(-d_pi);
948
949            for (coef_key, coef_val) in composite.coefficients.iter() {
951                let ck_index: usize = *column_names_map.get(coef_key).unwrap();
952                A_matrix.rows.push(row_i);
953                A_matrix.cols.push(ck_index);
954                A_matrix.vals.push(*coef_val);
955            }
956
957            let b_phi = composite.bias.0 + d_pi;
959            b_vector.push(-1 * b_phi);
960
961            if double_binding {
962
963                let phi_prim = composite.negate();
972                let phi_prim_ib = phi_prim.dot(&coef_bounds);
973                let d_phi_prim = std::cmp::max(phi_prim_ib.0.abs(), phi_prim_ib.1.abs());
974                let pi_coef = d_phi_prim - phi_prim.bias.0;
975
976                A_matrix.rows.push(row_i + 1);
978                A_matrix.cols.push(ki);
979                A_matrix.vals.push(pi_coef);
980
981                for (phi_coef_key, phi_coef_val) in phi_prim.coefficients.iter() {
983                    let ck_index: usize = *column_names_map.get(phi_coef_key).unwrap();
984                    A_matrix.rows.push(row_i + 1);
985                    A_matrix.cols.push(ck_index);
986                    A_matrix.vals.push(*phi_coef_val);
987                }
988
989                b_vector.push(-1 * phi_prim.bias.0);
991
992                row_i += 1;
994            }
995
996            row_i += 1;
998        }
999
1000        if fixed_constraints {
1001            let mut fixed_bound_map: HashMap<i64, Vec<usize>> = HashMap::new();
1004            for (key, bound) in primitives.iter().filter(|(_, bound)| bound_fixed(**bound)) {
1005                fixed_bound_map.entry(bound.0).or_insert_with(Vec::new).push(*column_names_map.get(&key.to_string()).unwrap());
1006            }
1007    
1008            for (v, primitive_ids) in fixed_bound_map.iter() {
1009                let b = *v * primitive_ids.len() as i64;
1010                for i in vec![-1, 1] {
1011                    for primitive_id in primitive_ids {
1012                        A_matrix.rows.push(row_i);
1013                        A_matrix.cols.push(*primitive_id);
1014                        A_matrix.vals.push(i);
1015                    }
1016                    b_vector.push(i * b);
1017                    row_i += 1;
1018                }
1019            }
1020        }
1021
1022        let mut integer_variables: Vec<String> = Vec::new();
1024
1025        for (p_key, p_bound) in primitives.iter().filter(|(_, bound)| bound.0 < 0 || bound.1 > 1) {
1027            
1028            integer_variables.push(p_key.to_string());
1030            
1031            if integer_constraints {
1032                let pi = *column_names_map.get(&p_key.to_string()).unwrap();
1034                
1035                if p_bound.0 < 0 {
1036                    A_matrix.rows.push(row_i);
1037                    A_matrix.cols.push(pi);
1038                    A_matrix.vals.push(-1);
1039                    b_vector.push(-1 * p_bound.0);
1040                    row_i += 1;
1041                }
1042    
1043                if p_bound.1 > 1 {
1044                    A_matrix.rows.push(row_i);
1045                    A_matrix.cols.push(pi);
1046                    A_matrix.vals.push(1);
1047                    b_vector.push(p_bound.1);
1048                    row_i += 1;
1049                } 
1050            }
1051        }
1052
1053        A_matrix.shape = (row_i, column_names_map.len());
1055
1056        let polyhedron = SparsePolyhedron {
1058            A: A_matrix,
1059            b: b_vector,
1060            columns: column_names_map.keys().cloned().collect(),
1061            integer_columns: integer_variables,
1062        };
1063
1064        return polyhedron;
1065    }
1066
1067    pub fn to_sparse_polyhedron_default(&self) -> SparsePolyhedron {
1075        self.to_sparse_polyhedron(true, true, true)
1076    }
1077
1078    pub fn to_dense_polyhedron(&self, double_binding: bool, integer_constraints: bool, fixed_constraints: bool) -> DensePolyhedron {
1088        DensePolyhedron::from(self.to_sparse_polyhedron(double_binding, integer_constraints, fixed_constraints))
1089    }
1090
1091    pub fn to_dense_polyhedron_default(&self) -> DensePolyhedron {
1096        self.to_dense_polyhedron(true, true, true)
1097    }
1098    
1099    pub fn set_coef(&mut self, id: ID, coefficient: f64) {
1108        if let Some(node) = self.nodes.get_mut(&id) {
1110            node.coefficient = coefficient;
1111        }
1112    }
1113    
1114    pub fn get_coef(&self, id: &ID) -> f64 {
1122        self.nodes.get(id).map(|node| node.coefficient).unwrap_or(0.0)
1124    }
1125    
1126    pub fn set_primitive(&mut self, id: ID, bound: Bound) {
1135        self.nodes.insert(id.clone(), Node {
1137            expression: BoolExpression::Primitive(bound),
1138            coefficient: 0.0,
1139        });
1140    }
1141
1142    pub fn set_primitives(&mut self, ids: Vec<ID>, bound: Bound) {
1151        let unique_ids: IndexSet<_> = ids.into_iter().collect();
1152        for id in unique_ids {
1153            self.set_primitive(id, bound);
1154        }
1155    }
1156
1157    pub fn set_gelineq(&mut self, coefficient_variables: Vec<Coefficient>, bias: i64) -> ID {
1169        let mut unique_coefficients: IndexMap<ID, i64> = IndexMap::new();
1171        for (key, value) in coefficient_variables {
1172            *unique_coefficients.entry(key).or_insert(0) += value;
1173        }
1174        let coefficient_variables: Vec<Coefficient> = unique_coefficients.into_iter().collect();
1175
1176        let hash = create_hash(&coefficient_variables, bias);
1178        
1179        let id = hash.to_string();
1181
1182        self.nodes.insert(id.clone(), Node {
1184            expression: BoolExpression::Composite(Constraint { coefficients: coefficient_variables, bias: (bias, bias) }),
1185            coefficient: 0.0,
1186        });
1187
1188        return id;
1189    }
1190
1191    pub fn set_atleast(&mut self, references: Vec<ID>, value: i64) -> ID {
1200        let unique_references: IndexSet<_> = references.into_iter().collect();
1201        self.set_gelineq(unique_references.into_iter().map(|x| (x, 1)).collect(), -value)
1202    }
1203
1204    pub fn set_atmost(&mut self, references: Vec<ID>, value: i64) -> ID {
1213        let unique_references: IndexSet<_> = references.into_iter().collect();
1214        self.set_gelineq(unique_references.into_iter().map(|x| (x, -1)).collect(), value)
1215    }
1216
1217    pub fn set_equal(&mut self, references: Vec<ID>, value: i64) -> ID {
1228        let unique_references: IndexSet<_> = references.into_iter().collect();
1229        let ub = self.set_atleast(unique_references.clone().into_iter().collect(), value);
1230        let lb = self.set_atmost(unique_references.into_iter().collect(), value);
1231        self.set_and(vec![ub, lb])
1232    }
1233
1234    pub fn set_and(&mut self, references: Vec<ID>) -> ID {
1245        let unique_references: IndexSet<_> = references.into_iter().collect();
1246        let length = unique_references.len();
1247        self.set_atleast(unique_references.into_iter().collect(), length as i64)
1248    }
1249
1250    pub fn set_or(&mut self, references: Vec<ID>) -> ID {
1261        let unique_references: IndexSet<_> = references.into_iter().collect();
1262        self.set_atleast(unique_references.into_iter().collect(), 1)
1263    }
1264
1265    pub fn set_nand(&mut self, references: Vec<ID>) -> ID {
1276        let unique_references: IndexSet<_> = references.into_iter().collect();
1277        let length = unique_references.len();
1278        self.set_atmost(unique_references.into_iter().collect(), length as i64 - 1)
1279    }
1280    
1281    pub fn set_nor(&mut self, references: Vec<ID>) -> ID {
1292        let unique_references: IndexSet<_> = references.into_iter().collect();
1293        self.set_atmost(unique_references.into_iter().collect(), 0)
1294    }
1295
1296    pub fn set_not(&mut self, references: Vec<ID>) -> ID {
1307        let unique_references: IndexSet<_> = references.into_iter().collect();
1308        self.set_atmost(unique_references.into_iter().collect(), 0)
1309    }
1310
1311    pub fn set_xor(&mut self, references: Vec<ID>) -> ID {
1322        let unique_references: IndexSet<_> = references.into_iter().collect();
1323        let atleast = self.set_or(unique_references.clone().into_iter().collect());
1324        let atmost = self.set_atmost(unique_references.into_iter().collect(), 1);
1325        self.set_and(vec![atleast, atmost])
1326    }
1327
1328    pub fn set_xnor(&mut self, references: Vec<ID>) -> ID {
1339        let unique_references: IndexSet<_> = references.into_iter().collect();
1340        let atleast = self.set_atleast(unique_references.clone().into_iter().collect(), 2);
1341        let atmost = self.set_atmost(unique_references.into_iter().collect(), 0);
1342        self.set_or(vec![atleast, atmost])
1343    }
1344
1345    pub fn set_imply(&mut self, condition: ID, consequence: ID) -> ID {
1357        let not_condition = self.set_not(vec![condition]);
1358        self.set_or(vec![not_condition, consequence])
1359    }
1360
1361    pub fn set_equiv(&mut self, lhs: ID, rhs: ID) -> ID {
1373        let imply_lr = self.set_imply(lhs.clone(), rhs.clone());
1374        let imply_rl = self.set_imply(rhs.clone(), lhs.clone());
1375        self.set_and(vec![imply_lr, imply_rl])
1376    }
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381    use super::*;
1382
1383    fn evaluate_model_polyhedron(
1390        model: &Pldag,
1391        poly: &DensePolyhedron,
1392        root: &String
1393    ) {
1394        for combo in model.primitive_combinations() {
1395            let interp = combo.iter()
1397                .map(|(k,&v)| (k.clone(), (v,v)))
1398                .collect::<IndexMap<String,Bound>>();
1399
1400            let prop = model.propagate(&interp);
1402            let model_root_val = *prop.get(root).unwrap();
1403
1404            let mut assumption = HashMap::new();
1406            assumption.insert(root.clone(), 1);
1407            let shrunk = poly.assume(&assumption);
1408
1409            let poly_val = shrunk.evaluate(&prop);
1411            assert_eq!(
1412                poly_val,
1413                model_root_val,
1414                "Disagreement on {:?}: model={:?}, poly={:?}",
1415                combo,
1416                model_root_val,
1417                poly_val
1418            );
1419        }
1420    }
1421
1422    #[test]
1423    fn test_propagate() {
1424        let mut model = Pldag::new();
1425        model.set_primitive("x".to_string(), (0, 1));
1426        model.set_primitive("y".to_string(), (0, 1));
1427        let root = model.set_and(vec!["x".to_string(), "y".to_string()]);
1428
1429        let result = model.propagate(&IndexMap::new());
1430        assert_eq!(result.get("x").unwrap(), &(0, 1));
1431        assert_eq!(result.get("y").unwrap(), &(0, 1));
1432        assert_eq!(result.get(&root).unwrap(), &(0, 1));
1433
1434        let mut assignments = IndexMap::new();
1435        assignments.insert("x".to_string(), (1, 1));
1436        assignments.insert("y".to_string(), (1, 1));
1437        let result = model.propagate(&assignments);
1438        assert_eq!(result.get(&root).unwrap(), &(1, 1));
1439
1440        let mut model = Pldag::new();
1441        model.set_primitive("x".to_string(), (0, 1));
1442        model.set_primitive("y".to_string(), (0, 1));
1443        model.set_primitive("z".to_string(), (0, 1));
1444        let root = model.set_xor(vec!["x".to_string(), "y".to_string(), "z".to_string()]);
1445        let result = model.propagate(&IndexMap::new());
1446        assert_eq!(result.get("x").unwrap(), &(0, 1));
1447        assert_eq!(result.get("y").unwrap(), &(0, 1));
1448        assert_eq!(result.get("z").unwrap(), &(0, 1));
1449        assert_eq!(result.get(&root).unwrap(), &(0, 1));
1450
1451        let mut assignments = IndexMap::new();
1452        assignments.insert("x".to_string(), (1, 1));
1453        assignments.insert("y".to_string(), (1, 1));
1454        assignments.insert("z".to_string(), (1, 1));
1455        let result = model.propagate(&assignments);
1456        assert_eq!(result.get(&root).unwrap(), &(0, 0));
1457        
1458        let mut assignments = IndexMap::new();
1459        assignments.insert("x".to_string(), (0, 1));
1460        assignments.insert("y".to_string(), (1, 1));
1461        assignments.insert("z".to_string(), (1, 1));
1462        let result = model.propagate(&assignments);
1463        assert_eq!(result.get(&root).unwrap(), &(0, 0));
1464        
1465        let mut assignments = IndexMap::new();
1466        assignments.insert("x".to_string(), (0, 0));
1467        assignments.insert("y".to_string(), (1, 1));
1468        assignments.insert("z".to_string(), (0, 0));
1469        let result = model.propagate(&assignments);
1470        assert_eq!(result.get(&root).unwrap(), &(1, 1));
1471    }
1472
1473    #[test]
1475    fn test_propagate_or_gate() {
1476        let mut model = Pldag::new();
1477        model.set_primitive("a".into(), (0, 1));
1478        model.set_primitive("b".into(), (0, 1));
1479        let or_root = model.set_or(vec!["a".into(), "b".into()]);
1480
1481        let res = model.propagate(&IndexMap::new());
1483        assert_eq!(res["a"], (0, 1));
1484        assert_eq!(res["b"], (0, 1));
1485        assert_eq!(res[&or_root], (0, 1));
1486
1487        let mut interp = IndexMap::new();
1489        interp.insert("a".into(), (1, 1));
1490        let res = model.propagate(&interp);
1491        assert_eq!(res[&or_root], (1, 1));
1492
1493        let mut interp = IndexMap::new();
1495        interp.insert("a".into(), (0, 0));
1496        interp.insert("b".into(), (0, 0));
1497        let res = model.propagate(&interp);
1498        assert_eq!(res[&or_root], (0, 0));
1499
1500        let mut interp = IndexMap::new();
1502        interp.insert("b".into(), (0, 0));
1503        let res = model.propagate(&interp);
1504        assert_eq!(res[&or_root], (0, 1));
1505    }
1506
1507    #[test]
1509    fn test_propagate_not_gate() {
1510        let mut model = Pldag::new();
1511        model.set_primitive("p".into(), (0, 1));
1512        let not_root = model.set_not(vec!["p".into()]);
1513
1514        let res = model.propagate(&IndexMap::new());
1516        assert_eq!(res["p"], (0, 1));
1517        assert_eq!(res[¬_root], (0, 1));
1518
1519        let mut interp = IndexMap::new();
1521        interp.insert("p".into(), (0, 0));
1522        let res = model.propagate(&interp);
1523        assert_eq!(res[¬_root], (1, 1));
1524
1525        let mut interp = IndexMap::new();
1527        interp.insert("p".into(), (1, 1));
1528        let res = model.propagate(&interp);
1529        assert_eq!(res[¬_root], (0, 0));
1530    }
1531
1532    #[test]
1533    fn test_to_polyhedron_and() {
1534        let mut m = Pldag::new();
1535        m.set_primitive("x".into(), (0,1));
1536        m.set_primitive("y".into(), (0,1));
1537        let root = m.set_and(vec!["x".into(), "y".into()]);
1538        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1539        evaluate_model_polyhedron(&m, &poly, &root);
1540    }
1541
1542    #[test]
1543    fn test_to_polyhedron_or() {
1544        let mut m = Pldag::new();
1545        m.set_primitive("a".into(), (0,1));
1546        m.set_primitive("b".into(), (0,1));
1547        m.set_primitive("c".into(), (0,1));
1548        let root = m.set_or(vec!["a".into(), "b".into(), "c".into()]);
1549        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1550        evaluate_model_polyhedron(&m, &poly, &root);
1551    }
1552
1553    #[test]
1554    fn test_to_polyhedron_not() {
1555        let mut m = Pldag::new();
1556        m.set_primitive("p".into(), (0,1));
1557        let root = m.set_not(vec!["p".into()]);
1558        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1559        evaluate_model_polyhedron(&m, &poly, &root);
1560    }
1561
1562    #[test]
1563    fn test_to_polyhedron_xor() {
1564        let mut m = Pldag::new();
1565        m.set_primitive("x".into(), (0,1));
1566        m.set_primitive("y".into(), (0,1));
1567        m.set_primitive("z".into(), (0,1));
1568        let root = m.set_xor(vec!["x".into(), "y".into(), "z".into()]);
1569        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1570        evaluate_model_polyhedron(&m, &poly, &root);
1571    }
1572
1573    #[test]
1574    fn test_to_polyhedron_nested() {
1575        let mut m = Pldag::new();
1578        m.set_primitive("x".into(), (0,1));
1579        m.set_primitive("y".into(), (0,1));
1580        m.set_primitive("z".into(), (0,1));
1581
1582        let w = m.set_and(vec!["x".into(), "y".into()]);
1583        let nz = m.set_not(vec!["z".into()]);
1584        let v = m.set_or(vec![w.clone(), nz.clone()]);
1585
1586        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1587        evaluate_model_polyhedron(&m, &poly, &v);
1588    }
1589
1590    #[test]
1593    fn test_propagate_nested_composite() {
1594        let mut model = Pldag::new();
1595        model.set_primitive("x".into(), (0, 1));
1596        model.set_primitive("y".into(), (0, 1));
1597        model.set_primitive("z".into(), (0, 1));
1598
1599        let w = model.set_and(vec!["x".into(), "y".into()]);
1600        let v = model.set_xor(vec![w.clone(), "z".into()]);
1601
1602        let res = model.propagate(&IndexMap::new());
1604        for var in &["x","y","z"] {
1605            assert_eq!(res[*var], (0,1), "{}", var);
1606        }
1607        assert_eq!(res[&w], (0,1));
1608        assert_eq!(res[&v], (0,1));
1609
1610        let mut interp = IndexMap::new();
1612        interp.insert("x".into(), (1,1));
1613        interp.insert("y".into(), (1,1));
1614        interp.insert("z".into(), (0,0));
1615        let res = model.propagate(&interp);
1616        assert_eq!(res[&w], (1,1));
1617        assert_eq!(res[&v], (1,1));
1618
1619        let mut interp = IndexMap::new();
1621        interp.insert("x".into(), (0,0));
1622        interp.insert("y".into(), (1,1));
1623        interp.insert("z".into(), (1,1));
1624        let res = model.propagate(&interp);
1625        assert_eq!(res[&w], (0,0));
1626        assert_eq!(res[&v], (1,1));
1627
1628        let mut interp = IndexMap::new();
1630        interp.insert("x".into(), (0,0));
1631        interp.insert("y".into(), (0,0));
1632        interp.insert("z".into(), (0,0));
1633        let res = model.propagate(&interp);
1634        assert_eq!(res[&w], (0,0));
1635        assert_eq!(res[&v], (0,0));
1636    }
1637
1638    #[test]
1642    fn test_propagate_out_of_bounds_does_not_crash() {
1643        let mut model = Pldag::new();
1644        model.set_primitive("u".into(), (0, 1));
1645        let root = model.set_not(vec!["u".into()]);
1646
1647        let mut interp = IndexMap::new();
1648        interp.insert("u".into(), (5,5));
1650        let res = model.propagate(&interp);
1651
1652        assert_eq!(res["u"], (5,5));
1654        let _ = res[&root];
1658    }
1659
1660    #[test]
1661    fn test_to_polyhedron() {
1662
1663        fn evaluate_model_polyhedron(model: &Pldag, polyhedron: &DensePolyhedron, root: &String) {
1664            for combination in model.primitive_combinations() {
1665                let assignments = combination
1666                    .iter()
1667                    .map(|(k, &v)| (k.clone(), (v, v)))
1668                    .collect::<IndexMap<String, Bound>>();
1669                let model_prop = model.propagate(&assignments);
1670                let model_eval = *model_prop.get(root).unwrap();
1671                let mut assumption = HashMap::new();
1672                assumption.insert(root.clone(), 1);
1673                let assumed_polyhedron = polyhedron.assume(&assumption);
1674                let assumed_poly_eval = assumed_polyhedron.evaluate(&model_prop);
1675                assert_eq!(assumed_poly_eval, model_eval);
1676            }
1677        }
1678
1679        let mut model: Pldag = Pldag::new();
1680        model.set_primitive("x".to_string(), (0, 1));
1681        model.set_primitive("y".to_string(), (0, 1));
1682        model.set_primitive("z".to_string(), (0, 1));
1683        let root = model.set_xor(vec!["x".to_string(), "y".to_string(), "z".to_string()]);
1684        let polyhedron: DensePolyhedron = model.to_sparse_polyhedron_default().into();
1685        evaluate_model_polyhedron(&model, &polyhedron, &root);
1686
1687        let mut model = Pldag::new();
1688        model.set_primitive("x".to_string(), (0, 1));
1689        model.set_primitive("y".to_string(), (0, 1));
1690        let root = model.set_and(vec!["x".to_string(), "y".to_string()]);
1691        let polyhedron = model.to_sparse_polyhedron_default().into();
1692        evaluate_model_polyhedron(&model, &polyhedron, &root);
1693
1694        let mut model: Pldag = Pldag::new();
1695        model.set_primitive("x".to_string(), (0, 1));
1696        model.set_primitive("y".to_string(), (0, 1));
1697        model.set_primitive("z".to_string(), (0, 1));
1698        let root = model.set_xor(vec!["x".to_string(), "y".to_string(), "z".to_string()]);
1699        let polyhedron = model.to_sparse_polyhedron_default().into();
1700        evaluate_model_polyhedron(&model, &polyhedron, &root);
1701    }
1702
1703    #[test]
1705    fn test_to_polyhedron_single_operand_identity() {
1706        {
1708            let mut m = Pldag::new();
1709            m.set_primitive("x".into(), (0,1));
1710            let root = m.set_and(vec!["x".into()]);
1711            let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1712            evaluate_model_polyhedron(&m, &poly, &root);
1713        }
1714        {
1716            let mut m = Pldag::new();
1717            m.set_primitive("y".into(), (0,1));
1718            let root = m.set_or(vec!["y".into()]);
1719            let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1720            evaluate_model_polyhedron(&m, &poly, &root);
1721        }
1722        {
1724            let mut m = Pldag::new();
1725            m.set_primitive("z".into(), (0,1));
1726            let root = m.set_xor(vec!["z".into()]);
1727            let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1728            evaluate_model_polyhedron(&m, &poly, &root);
1729        }
1730    }
1731
1732    #[test]
1734    fn test_to_polyhedron_duplicate_operands_and() {
1735        let mut m = Pldag::new();
1736        m.set_primitive("x".into(), (0,1));
1737        let root = m.set_and(vec!["x".into(), "x".into()]);
1738        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1739        evaluate_model_polyhedron(&m, &poly, &root);
1740    }
1741
1742    #[test]
1748    fn test_to_polyhedron_deeply_nested_chain() {
1749        let mut m = Pldag::new();
1750        for &v in &["a","b","c","d","e"] {
1752            m.set_primitive(v.into(), (0,1));
1753        }
1754        let a = "a".to_string();
1755        let b = "b".to_string();
1756        let c = "c".to_string();
1757        let d = "d".to_string();
1758
1759        let w1 = m.set_and(vec![a.clone(), b.clone()]);
1760        let w2 = m.set_or(vec![w1.clone(), c.clone()]);
1761        let w3 = m.set_xor(vec![w2.clone(), d.clone()]);
1762        let root = m.set_not(vec![w3.clone()]);
1763
1764        let poly: DensePolyhedron = m.to_sparse_polyhedron_default().into();
1765        evaluate_model_polyhedron(&m, &poly, &root);
1766    }
1767
1768    #[test]
1769    fn make_simple_dag() {
1770        let mut pldag = Pldag::new();
1771        pldag.set_primitive("b".into(), (0, 1));
1772        pldag.set_primitive("d".into(), (0, 1));
1773        pldag.set_primitive("e".into(), (0, 1));
1774        let c = pldag.set_or(vec!["d".into(), "e".into()]);
1775        let a = pldag.set_or(vec!["b".into(), c.clone()]);
1776        let deps = pldag.transitive_dependencies();
1777        let expect = |xs: &[&str]| {
1778            xs.iter().cloned().map(String::from).collect::<HashSet<_>>()
1779        };
1780        assert_eq!(deps.get(&a), Some(&expect(&["b", &c.to_string(), "d", "e"])));
1781        assert_eq!(deps.get(&c), Some(&expect(&["d", "e"])));
1782        assert_eq!(deps.get("b"), Some(&expect(&[])));
1783        assert_eq!(deps.get("d"), Some(&expect(&[])));
1784        assert_eq!(deps.get("e"), Some(&expect(&[])));
1785    }
1786
1787    #[test]
1788    fn test_chain_dag() {
1789        let mut pldag = Pldag::new();
1791        pldag.set_primitive("z".into(), (0, 0));;
1792        let y = pldag.set_or(vec!["z".into()]);
1793        let x = pldag.set_or(vec![y.clone()]);
1794        let deps = pldag.transitive_dependencies();
1795
1796        let expect = |xs: &[&str]| {
1797            xs.iter().cloned().map(String::from).collect::<HashSet<_>>()
1798        };
1799
1800        assert_eq!(deps.get(&x), Some(&expect(&[&y.to_string(), "z"])));
1801        assert_eq!(deps.get(&y), Some(&expect(&["z"])));
1802        assert_eq!(deps.get("z"), Some(&expect(&[])));
1803    }
1804
1805    #[test]
1806    fn test_all_primitives() {
1807        let mut pldag = Pldag::new();
1809        for &name in &["p", "q", "r"] {
1810            pldag.nodes.insert(name.into(), Node {
1811                expression: BoolExpression::Primitive((1, 5)),
1812                coefficient: 0.0,
1813            });
1814        }
1815        let deps = pldag.transitive_dependencies();
1816
1817        for &name in &["p", "q", "r"] {
1818            assert!(deps.get(name).unwrap().is_empty(), "{} should have no deps", name);
1819        }
1820    }
1821
1822    #[test]
1823    fn test_propagate_weighted() {
1824        let mut model = Pldag::new();
1825        model.set_primitive("x".to_string(), (0, 1));
1826        model.set_primitive("y".to_string(), (0, 1));
1827        let root = model.set_and(vec!["x".to_string(), "y".to_string()]);
1828        
1829        model.set_coef("x".to_string(), 2.0);
1831        model.set_coef("y".to_string(), 3.0);
1832        
1833        let mut assignments = IndexMap::new();
1834        assignments.insert("x".to_string(), (1, 1));
1835        assignments.insert("y".to_string(), (1, 1));
1836        
1837        let propagated = model.propagate_coefs(&assignments);
1838        
1839        assert_eq!(propagated.get("x").unwrap().0, (1, 1)); assert_eq!(propagated.get("x").unwrap().1, (2.0, 2.0)); assert_eq!(propagated.get("y").unwrap().0, (1, 1)); assert_eq!(propagated.get("y").unwrap().1, (3.0, 3.0)); assert_eq!(propagated.get(&root).unwrap().0, (1, 1)); assert_eq!(propagated.get(&root).unwrap().1, (5.0, 5.0)); }
1847
1848    #[test]
1849    fn test_readme_example() {
1850        let mut pldag: Pldag = Pldag::new();
1854
1855        pldag.set_primitive("x".to_string(), (0, 1));
1857        pldag.set_primitive("y".to_string(), (0, 1));
1858        pldag.set_primitive("z".to_string(), (0, 1));
1859
1860        let root = pldag.set_or(vec![
1862            "x".to_string(),
1863            "y".to_string(),
1864            "z".to_string(),
1865        ]);
1866
1867        let mut inputs: IndexMap<String, Bound> = IndexMap::new();
1869        let validited = pldag.propagate(&inputs);
1870        println!("Root valid? {}", *validited.get(&root).unwrap() == (1, 1)); inputs.insert("x".to_string(), (0,0));
1876        let revalidited = pldag.propagate(&inputs);
1877        println!("Root valid? {}", *revalidited.get(&root).unwrap() == (1, 1)); inputs.insert("y".to_string(), (1,1));
1881        inputs.insert("z".to_string(), (1,1));
1882        let revalidited = pldag.propagate(&inputs);
1883        println!("Root valid? {}", *revalidited.get(&root).unwrap() == (1, 1)); pldag.set_coef("x".to_string(), 1.0);
1888        pldag.set_coef("y".to_string(), 2.0);
1889        pldag.set_coef("z".to_string(), 3.0);
1890        pldag.set_coef(root.clone(), -1.0);
1892        let scores = pldag.propagate_coefs(&inputs);
1893        println!("Total score: {:?}", scores.get(&root).unwrap().1); inputs.insert("x".to_string(), (0,1));
1897        let scores = pldag.propagate_coefs(&inputs);
1898        println!("Total score: {:?}", scores.get(&root).unwrap().1); inputs.insert("x".to_string(), (0,0));
1904        let scores = pldag.propagate_coefs(&inputs);
1905        println!("Total score: {:?}", scores.get(&root).unwrap().1); inputs.insert("y".to_string(), (0,0));
1909        inputs.insert("z".to_string(), (0,0));
1910        let scores = pldag.propagate_coefs(&inputs);
1911        println!("Total score: {:?}", scores.get(&root).unwrap().1); }
1913
1914    #[test]
1915    fn test_get_objective() {
1916        let mut model = Pldag::new();
1917        
1918        model.set_primitive("x".to_string(), (0, 1));
1920        model.set_primitive("y".to_string(), (0, 1));
1921        model.set_primitive("z".to_string(), (0, 1));
1922        
1923        let root = model.set_and(vec!["x".to_string(), "y".to_string()]);
1925        
1926        let coeffs = model.get_objective();
1928        assert_eq!(coeffs.len(), 3); assert_eq!(coeffs.get("x"), Some(&0.0));
1930        assert_eq!(coeffs.get("y"), Some(&0.0));
1931        assert_eq!(coeffs.get("z"), Some(&0.0));
1932        assert_eq!(coeffs.get(&root), None); model.set_coef("x".to_string(), 2.5);
1936        model.set_coef("y".to_string(), -1.0);
1937        model.set_coef("z".to_string(), 3.14);
1938        model.set_coef(root.clone(), 10.0); let coeffs = model.get_objective();
1942        assert_eq!(coeffs.len(), 3); assert_eq!(coeffs.get("x"), Some(&2.5));
1944        assert_eq!(coeffs.get("y"), Some(&-1.0));
1945        assert_eq!(coeffs.get("z"), Some(&3.14));
1946        assert_eq!(coeffs.get(&root), None); let keys: Vec<&String> = coeffs.keys().collect();
1950        assert_eq!(keys, vec!["x", "y", "z"]);
1951        
1952        let mut model_no_primitives = Pldag::new();
1954        model_no_primitives.set_primitive("a".to_string(), (0, 1));
1955        model_no_primitives.set_primitive("b".to_string(), (0, 1));
1956        let composite1 = model_no_primitives.set_and(vec!["a".to_string(), "b".to_string()]);
1957        let composite2 = model_no_primitives.set_or(vec![composite1.clone()]);
1958        
1959        let coeffs_mixed = model_no_primitives.get_objective();
1962        assert_eq!(coeffs_mixed.len(), 2); assert!(coeffs_mixed.contains_key("a"));
1964        assert!(coeffs_mixed.contains_key("b"));
1965        assert!(!coeffs_mixed.contains_key(&composite1));
1966        assert!(!coeffs_mixed.contains_key(&composite2));
1967    }
1968
1969    
1970}