1#[cfg(feature = "visualize")]
13use std::{ffi::CString, os::fd::AsRawFd};
14
15#[cfg(feature = "config_deserialize")]
16use serde::Deserialize;
17
18use std::{
19 fmt::{Debug, Display},
20 ptr::null,
21 rc::Rc,
22 sync::Mutex,
23};
24
25use cudd_sys::cudd::{
26 Cudd_CountPathsToNonZero, Cudd_Init, Cudd_Not, Cudd_PrintDebug, Cudd_PrintInfo,
27 Cudd_PrintMinterm, Cudd_Quit, Cudd_ReadLogicZero, Cudd_ReadOne, Cudd_RecursiveDeref, Cudd_Ref,
28 Cudd_bddAnd, Cudd_bddComputeCube, Cudd_bddExistAbstract, Cudd_bddIthVar, Cudd_bddOr,
29 Cudd_bddSwapVariables,
30};
31
32#[cfg(feature = "visualize")]
33use cudd_sys::cudd::Cudd_DumpDot;
34
35pub use cudd_sys::cudd::{CUDD_CACHE_SLOTS, CUDD_UNIQUE_SLOTS};
37
38use super::{Bdd, BddManager};
39
40type InternalCuddDD = *mut cudd_sys::DdNode;
41
42pub struct CuddDD {
44 mgr: InternalCuddMgr,
45 node: InternalCuddDD,
46}
47
48impl CuddDD {
49 fn new(mgr: InternalCuddMgr, node: InternalCuddDD) -> Self {
50 assert!(!node.is_null());
51
52 unsafe { Cudd_Ref(node) };
53
54 CuddDD { mgr, node }
55 }
56}
57
58impl Clone for CuddDD {
59 fn clone(&self) -> Self {
60 unsafe { Cudd_Ref(self.node) };
61 Self {
62 mgr: self.mgr.clone(),
63 node: self.node,
64 }
65 }
66}
67
68impl Drop for CuddDD {
69 fn drop(&mut self) {
70 assert!(!self.node.is_null());
71 unsafe { Cudd_RecursiveDeref(self.mgr.0, self.node) };
72 }
73}
74
75impl PartialEq for CuddDD {
76 fn eq(&self, other: &Self) -> bool {
78 let eq = self.equiv(other);
79 eq.satisfiable() && !eq.not().satisfiable()
80 }
81}
82
83impl Debug for CuddDD {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "CUDD Debug Info: ")?;
86 unsafe { Cudd_PrintDebug(self.mgr.0, self.node, 0, 10) };
87 write!(f, ", Node Addr: {:#?}", self.node)
88 }
89}
90
91impl Display for CuddDD {
92 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 unsafe { Cudd_PrintMinterm(self.mgr.0, self.node) };
94 Ok(())
95 }
96}
97
98impl Bdd for CuddDD {
99 fn and(&self, rhs: &Self) -> Self {
100 assert!(self.mgr == rhs.mgr);
101
102 let new_node = unsafe { Cudd_bddAnd(self.mgr.0, self.node, rhs.node) };
103 Self::new(self.mgr.clone(), new_node)
104 }
105
106 fn or(&self, rhs: &Self) -> Self {
107 assert!(self.mgr == rhs.mgr);
108
109 let new_node = unsafe { Cudd_bddOr(self.mgr.0, self.node, rhs.node) };
110 CuddDD::new(self.mgr.clone(), new_node)
111 }
112
113 fn not(&self) -> Self {
114 let new_node = unsafe { Cudd_Not(self.node) };
115 Self::new(self.mgr.clone(), new_node)
116 }
117
118 fn exists<'a, I: IntoIterator<Item = &'a Self>>(&'a self, vars: I) -> Self {
119 let mut vars = vars
120 .into_iter()
121 .map(|dd| {
122 assert!(self.mgr == dd.mgr);
123 dd.node
124 })
125 .collect::<Vec<_>>();
126
127 let cube = unsafe {
128 Cudd_bddComputeCube(
129 self.mgr.0,
130 vars.as_mut_ptr(),
131 null::<i32>() as _,
132 vars.len() as _,
133 )
134 };
135 let cube = CuddDD::new(self.mgr.clone(), cube);
136
137 let exist_node = unsafe { Cudd_bddExistAbstract(self.mgr.0, self.node, cube.node) };
138 CuddDD::new(self.mgr.clone(), exist_node)
139 }
140
141 fn satisfiable(&self) -> bool {
142 let n = unsafe { Cudd_CountPathsToNonZero(self.node) };
143 n > 0.0
144 }
145
146 fn implies(&self, other: &Self) -> Self {
147 (self.not()).or(other)
149 }
150
151 fn equiv(&self, other: &Self) -> Self {
152 (self.and(other)).or(&self.not().and(&other.not()))
154 }
155
156 fn swap<'a, I: IntoIterator<Item = &'a Self>>(&'a self, from: I, to: I) -> Self {
157 let mut from = from
158 .into_iter()
159 .map(|dd| {
160 assert!(dd.mgr == self.mgr);
161 dd.node
162 })
163 .collect::<Vec<_>>();
164
165 let mut to = to
166 .into_iter()
167 .map(|bdd| {
168 assert!(bdd.mgr == self.mgr);
169 bdd.node
170 })
171 .collect::<Vec<_>>();
172
173 assert!(to.len() == from.len(), "Invalid permutation handed to CUDD");
174
175 let new_node = unsafe {
176 Cudd_bddSwapVariables(
177 self.mgr.0,
178 self.node,
179 from.as_mut_ptr(),
180 to.as_mut_ptr(),
181 from.len() as _,
182 )
183 };
184
185 CuddDD::new(self.mgr.clone(), new_node)
186 }
187}
188
189#[derive(PartialEq)]
191struct InternalCuddMgrPtr(*mut cudd_sys::DdManager);
192
193impl InternalCuddMgrPtr {
194 fn new() -> Self {
196 Self::new_with_config(&CuddManagerConfig::default())
197 }
198
199 fn new_with_config(config: &CuddManagerConfig) -> Self {
201 let mgr = unsafe {
202 Cudd_Init(
203 config.num_vars as _,
204 config.num_vars_z as _,
205 config.num_slots as _,
206 config.cache_slots as _,
207 config.max_memory as _,
208 )
209 };
210
211 assert!(!mgr.is_null(), "Failed to create CUDD Manager");
212
213 let reorder_method = config.reorder_method.into();
215 unsafe { cudd_sys::cudd::Cudd_AutodynEnable(mgr, reorder_method) };
216
217 InternalCuddMgrPtr(mgr)
218 }
219}
220
221impl Drop for InternalCuddMgrPtr {
223 fn drop(&mut self) {
224 unsafe { Cudd_Quit(self.0) };
225 }
226}
227
228type InternalCuddMgr = Rc<InternalCuddMgrPtr>;
230
231#[derive(Debug, Clone, PartialEq)]
237#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
238pub struct CuddManagerConfig {
239 #[cfg_attr(feature = "config_deserialize", serde(default = "default_num_vars"))]
242 pub num_vars: usize,
243 #[cfg_attr(feature = "config_deserialize", serde(default = "default_num_vars_z"))]
246 pub num_vars_z: usize,
247 #[cfg_attr(feature = "config_deserialize", serde(default = "default_num_slots"))]
250 pub num_slots: usize,
251 #[cfg_attr(feature = "config_deserialize", serde(default = "default_cache_slots"))]
254 pub cache_slots: usize,
255 #[cfg_attr(feature = "config_deserialize", serde(default = "default_max_memory"))]
260 pub max_memory: usize,
261 #[cfg_attr(
264 feature = "config_deserialize",
265 serde(default = "default_reorder_method")
266 )]
267 pub reorder_method: CuddReorderMethod,
268}
269
270fn default_num_vars() -> usize {
272 0
273}
274
275fn default_num_vars_z() -> usize {
277 0
278}
279
280fn default_num_slots() -> usize {
282 CUDD_UNIQUE_SLOTS as usize
283}
284fn default_cache_slots() -> usize {
286 CUDD_CACHE_SLOTS as usize
287}
288
289fn default_max_memory() -> usize {
291 0
292}
293
294fn default_reorder_method() -> CuddReorderMethod {
296 CuddReorderMethod::Sift
297}
298
299impl Default for CuddManagerConfig {
300 fn default() -> Self {
301 Self {
302 num_vars: default_num_vars(),
303 num_vars_z: default_num_vars_z(),
304 num_slots: default_num_slots(),
305 cache_slots: default_cache_slots(),
306 max_memory: default_max_memory(),
307 reorder_method: default_reorder_method(),
308 }
309 }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq)]
318#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
319pub enum CuddReorderMethod {
320 Disabled,
322 Random,
324 RandomPivot,
326 Sift,
328 SiftConverge,
330 SymmSift,
332 SymmSiftConverge,
334 Window2,
336 Window3,
338 Window4,
340 Window2Converge,
342 Window3Converge,
344 Window4Converge,
346 Annealing,
348 Genetic,
350 Exact,
352}
353
354impl From<CuddReorderMethod> for cudd_sys::cudd::Cudd_ReorderingType {
355 fn from(val: CuddReorderMethod) -> cudd_sys::cudd::Cudd_ReorderingType {
356 match val {
357 CuddReorderMethod::Disabled => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_NONE,
358 CuddReorderMethod::Random => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_RANDOM,
359 CuddReorderMethod::RandomPivot => {
360 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_RANDOM_PIVOT
361 }
362 CuddReorderMethod::Sift => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_SIFT,
363 CuddReorderMethod::SiftConverge => {
364 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_SIFT_CONVERGE
365 }
366 CuddReorderMethod::SymmSift => {
367 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_SYMM_SIFT
368 }
369 CuddReorderMethod::SymmSiftConverge => {
370 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_SYMM_SIFT_CONV
371 }
372 CuddReorderMethod::Window2 => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW2,
373 CuddReorderMethod::Window3 => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW3,
374 CuddReorderMethod::Window4 => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW4,
375 CuddReorderMethod::Window2Converge => {
376 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW2_CONV
377 }
378 CuddReorderMethod::Window3Converge => {
379 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW3_CONV
380 }
381 CuddReorderMethod::Window4Converge => {
382 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_WINDOW4_CONV
383 }
384 CuddReorderMethod::Annealing => {
385 cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_ANNEALING
386 }
387 CuddReorderMethod::Genetic => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_GENETIC,
388 CuddReorderMethod::Exact => cudd_sys::cudd::Cudd_ReorderingType::CUDD_REORDER_EXACT,
389 }
390 }
391}
392
393pub struct CuddManager {
400 mgr: InternalCuddMgr,
402 next_var_index: Rc<Mutex<usize>>,
404}
405
406impl CuddManager {
407 pub fn new() -> Self {
408 let mgr = InternalCuddMgrPtr::new();
409
410 CuddManager {
411 mgr: Rc::new(mgr),
412 next_var_index: Rc::new(Mutex::new(0)),
413 }
414 }
415
416 pub fn new_with_config(cfg: &CuddManagerConfig) -> Self {
417 let mgr = InternalCuddMgrPtr::new_with_config(cfg);
418
419 CuddManager {
420 mgr: Rc::new(mgr),
421 next_var_index: Rc::new(Mutex::new(0)),
422 }
423 }
424}
425
426impl Default for CuddManager {
427 fn default() -> Self {
428 Self::new()
429 }
430}
431
432impl Clone for CuddManager {
433 fn clone(&self) -> Self {
438 CuddManager {
439 mgr: Rc::clone(&self.mgr),
440 next_var_index: Rc::clone(&self.next_var_index),
441 }
442 }
443}
444
445impl PartialEq for CuddManager {
446 fn eq(&self, other: &Self) -> bool {
448 self.mgr == other.mgr
449 }
450}
451
452impl Debug for CuddManager {
453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454 writeln!(f, "CUDD manager Info: ")?;
455 unsafe { Cudd_PrintInfo(self.mgr.0, libc_stdhandle::stdout()) };
456 writeln!(
457 f,
458 "Mgr Addr: {:#?}, Number of variables: {}",
459 self.mgr.0,
460 self.next_var_index.lock().unwrap()
461 )
462 }
463}
464
465impl BddManager for CuddManager {
466 type DD = CuddDD;
467
468 fn new_var(&mut self) -> Self::DD {
469 let mut index = self.next_var_index.lock().unwrap();
470 let new_node = unsafe { Cudd_bddIthVar(self.mgr.0, *index as i32) };
471 *index += 1;
472
473 CuddDD::new(self.mgr.clone(), new_node)
474 }
475
476 fn get_bdd_false(&self) -> Self::DD {
477 let new_node = unsafe { Cudd_ReadLogicZero(self.mgr.0) };
478
479 CuddDD::new(self.mgr.clone(), new_node)
480 }
481
482 fn get_bdd_true(&self) -> Self::DD {
483 let new_node = unsafe { Cudd_ReadOne(self.mgr.0) };
484
485 CuddDD::new(self.mgr.clone(), new_node)
486 }
487
488 #[cfg(feature = "visualize")]
489 fn dump_dot<'a, VN, BN>(
490 &'a self,
491 file: std::fs::File,
492 bdd_names: impl IntoIterator<Item = (&'a Self::DD, BN)>,
493 variable_names: impl IntoIterator<Item = (&'a Self::DD, VN)>,
494 ) -> std::io::Result<()>
495 where
496 VN: std::fmt::Display + Clone,
497 BN: std::fmt::Display,
498 {
499 let res;
500 let (bdds_to_be_dumped, bdd_names) = bdd_names
501 .into_iter()
502 .map(|(dd, dn)| (dd.node, CString::new(dn.to_string()).unwrap()))
503 .collect::<(Vec<_>, Vec<_>)>();
504
505 let variable_names = variable_names
506 .into_iter()
507 .map(|(_, vn)| CString::new(vn.to_string()).unwrap())
508 .collect::<Vec<_>>();
509
510 if variable_names.len() < *self.next_var_index.lock().unwrap() && !variable_names.is_empty()
511 {
512 return Err(std::io::Error::other(
513 "Number of variable names does not match number of variables! Aborting as this will lead to a segfault",
514 ));
515 }
516
517 unsafe {
518 let file = libc::fdopen(file.as_raw_fd(), "w".as_ptr() as _);
519
520 let bdd_names = bdd_names
521 .into_iter()
522 .map(|n| n.into_raw())
523 .collect::<Vec<_>>();
524
525 let mut var_names_ptr = null();
526
527 let var_names = variable_names
528 .into_iter()
529 .map(|n| n.into_raw())
530 .collect::<Vec<_>>();
531
532 if !var_names.is_empty() {
533 var_names_ptr = var_names.as_ptr() as *const *const i8;
534 }
535
536 res = Cudd_DumpDot(
537 self.mgr.0,
538 bdds_to_be_dumped.len() as i32,
539 bdds_to_be_dumped.as_ptr() as *mut *mut cudd_sys::DdNode,
540 var_names_ptr,
541 bdd_names.as_ptr() as *const *const i8,
542 file,
543 );
544
545 libc::fflush(file);
546
547 let _ = var_names.into_iter().map(|p| CString::from_raw(p));
548 let _ = bdd_names.into_iter().map(|p| CString::from_raw(p));
549 };
550
551 if res == 1 {
552 Ok(())
553 } else {
554 Err(std::io::Error::other("Failed to dump DOT file"))
555 }
556 }
557}
558
559#[cfg(test)]
560mod test {
561 use crate::{
562 BddManager,
563 bdd_test_utils::{self, test_mgr_eq_and_clone},
564 };
565
566 use super::{CuddManager, CuddManagerConfig, CuddReorderMethod};
567
568 #[test]
569 fn test_mgr_eq() {
570 let mgr1 = CuddManager::default();
571 let mgr2 = CuddManager::default();
572
573 test_mgr_eq_and_clone(mgr1, mgr2);
574 }
575
576 #[test]
577 fn test_mgr_get() {
578 let mgr = CuddManager::default();
579 bdd_test_utils::test_mgr_get(mgr)
580 }
581
582 #[test]
583 fn test_not() {
584 let mgr = CuddManager::default();
585 bdd_test_utils::test_not(mgr)
586 }
587
588 #[test]
589 fn test_and() {
590 let mgr = CuddManager::default();
591 bdd_test_utils::test_and(mgr)
592 }
593
594 #[test]
595 fn test_or() {
596 let mgr = CuddManager::default();
597 bdd_test_utils::test_or(mgr)
598 }
599
600 #[test]
601 fn test_exists() {
602 let mgr = CuddManager::default();
603 bdd_test_utils::test_exists(mgr)
604 }
605
606 #[test]
607 fn test_implies() {
608 let mgr = CuddManager::default();
609 bdd_test_utils::test_implies(mgr)
610 }
611
612 #[test]
613 fn test_equiv() {
614 let mgr = CuddManager::default();
615 bdd_test_utils::test_equiv(mgr)
616 }
617
618 #[test]
619 fn test_swap() {
620 let mgr = CuddManager::default();
621 bdd_test_utils::test_swap(mgr)
622 }
623
624 #[test]
625 fn test_manager_with_custom_config() {
626 let cfg = CuddManagerConfig {
627 num_vars: 10,
628 num_vars_z: 0,
629 num_slots: 1000,
630 cache_slots: 1000,
631 max_memory: 0,
632 reorder_method: CuddReorderMethod::Disabled,
633 };
634
635 let mgr = CuddManager::new_with_config(&cfg);
636
637 bdd_test_utils::test_mgr_get(mgr.clone());
638 bdd_test_utils::test_not(mgr.clone());
639 bdd_test_utils::test_and(mgr.clone());
640 bdd_test_utils::test_or(mgr.clone());
641 bdd_test_utils::test_exists(mgr.clone());
642 }
643
644 #[test]
645 #[should_panic]
646 fn test_panic_on_bdd_of_mixed_mgrs() {
647 let mgr1 = CuddManager::default();
648 let mgr2 = CuddManager::default();
649 bdd_test_utils::panic_on_bdd_of_mixed_mgrs(mgr1, mgr2)
650 }
651
652 #[test]
653 fn print_funcs_work() {
654 let mut mgr = CuddManager::new();
655 let v = mgr.new_var();
656
657 println!("Var: {v}, Debug: {v:?}");
658 println!("Mgr Debug: {mgr:?}");
659 }
660
661 #[cfg(feature = "visualize")]
662 #[test]
663 fn test_abort_if_not_enough_var_names() {
664 let mut mgr = CuddManager::default();
665 let bdd1 = mgr.new_var();
666 let _ = mgr.new_var();
667
668 let file = std::fs::File::create("test.dot").unwrap();
669
670 let res = mgr.dump_dot(file, vec![(&bdd1, "BDD1")], vec![(&bdd1, "Var1")]);
671
672 assert!(res.is_err());
673 }
674}