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}