Skip to main content

oxiz_solver/mbqi/
macros.rs

1//! Macro Support for MBQI
2//!
3//! This module provides utility macros and helper functions for MBQI implementation.
4//! It includes macros for common patterns, debugging, and code generation.
5
6/// Macro for creating a quantified formula with default parameters
7#[macro_export]
8macro_rules! quantifier {
9    ($term:expr, $vars:expr, $body:expr, universal) => {
10        $crate::mbqi::QuantifiedFormula::new($term, $vars, $body, true)
11    };
12    ($term:expr, $vars:expr, $body:expr, existential) => {
13        $crate::mbqi::QuantifiedFormula::new($term, $vars, $body, false)
14    };
15}
16
17/// Macro for creating an instantiation
18#[macro_export]
19macro_rules! instantiation {
20    ($quantifier:expr, $subst:expr, $result:expr, $gen:expr) => {
21        $crate::mbqi::Instantiation::new($quantifier, $subst, $result, $gen)
22    };
23}
24
25/// Macro for debugging MBQI state
26#[macro_export]
27macro_rules! mbqi_debug {
28    ($($arg:tt)*) => {
29        #[cfg(feature = "mbqi-debug")]
30        {
31            eprintln!("[MBQI DEBUG] {}", format!($($arg)*));
32        }
33    };
34}
35
36/// Macro for MBQI tracing
37#[macro_export]
38macro_rules! mbqi_trace {
39    ($($arg:tt)*) => {
40        #[cfg(feature = "mbqi-trace")]
41        {
42            eprintln!("[MBQI TRACE] {}", format!($($arg)*));
43        }
44    };
45}
46
47/// Macro for timing MBQI operations
48#[macro_export]
49macro_rules! mbqi_time {
50    ($name:expr, $block:block) => {{
51        let start = std::time::Instant::now();
52        let result = $block;
53        let elapsed = start.elapsed();
54        mbqi_debug!("{} took {:?}", $name, elapsed);
55        result
56    }};
57}
58
59/// Macro for creating a model completion error
60#[macro_export]
61macro_rules! completion_error {
62    ($msg:expr) => {
63        $crate::mbqi::model_completion::CompletionError::CompletionFailed($msg.to_string())
64    };
65}
66
67/// Macro for creating a finder error
68#[macro_export]
69macro_rules! finder_error {
70    (unsat) => {
71        $crate::mbqi::finite_model::FinderError::UnsatAtBound
72    };
73    (max_bound) => {
74        $crate::mbqi::finite_model::FinderError::ExceededMaxBound
75    };
76    (resource) => {
77        $crate::mbqi::finite_model::FinderError::ResourceLimit
78    };
79}
80
81/// Utility functions for MBQI
82pub mod utils {
83    use lasso::Spur;
84    use oxiz_core::ast::{TermId, TermKind, TermManager};
85    use rustc_hash::{FxHashMap, FxHashSet};
86
87    /// Check if a term is ground (no free variables)
88    pub fn is_ground(term: TermId, manager: &TermManager) -> bool {
89        let mut visited = FxHashSet::default();
90        is_ground_rec(term, manager, &mut visited)
91    }
92
93    fn is_ground_rec(term: TermId, manager: &TermManager, visited: &mut FxHashSet<TermId>) -> bool {
94        if visited.contains(&term) {
95            return true;
96        }
97        visited.insert(term);
98
99        let Some(t) = manager.get(term) else {
100            return true;
101        };
102
103        if matches!(t.kind, TermKind::Var(_)) {
104            return false;
105        }
106
107        match &t.kind {
108            TermKind::And(args) | TermKind::Or(args) => {
109                for &arg in args {
110                    if !is_ground_rec(arg, manager, visited) {
111                        return false;
112                    }
113                }
114                true
115            }
116            TermKind::Not(arg) | TermKind::Neg(arg) => is_ground_rec(*arg, manager, visited),
117            TermKind::Apply { args, .. } => {
118                for &arg in args.iter() {
119                    if !is_ground_rec(arg, manager, visited) {
120                        return false;
121                    }
122                }
123                true
124            }
125            _ => true,
126        }
127    }
128
129    /// Collect all free variables in a term
130    pub fn free_vars(term: TermId, manager: &TermManager) -> FxHashSet<Spur> {
131        let mut vars = FxHashSet::default();
132        let mut visited = FxHashSet::default();
133        collect_vars_rec(term, &mut vars, &mut visited, manager);
134        vars
135    }
136
137    fn collect_vars_rec(
138        term: TermId,
139        vars: &mut FxHashSet<Spur>,
140        visited: &mut FxHashSet<TermId>,
141        manager: &TermManager,
142    ) {
143        if visited.contains(&term) {
144            return;
145        }
146        visited.insert(term);
147
148        let Some(t) = manager.get(term) else {
149            return;
150        };
151
152        if let TermKind::Var(name) = t.kind {
153            vars.insert(name);
154            return;
155        }
156
157        match &t.kind {
158            TermKind::And(args) | TermKind::Or(args) => {
159                for &arg in args {
160                    collect_vars_rec(arg, vars, visited, manager);
161                }
162            }
163            TermKind::Not(arg) | TermKind::Neg(arg) => {
164                collect_vars_rec(*arg, vars, visited, manager);
165            }
166            TermKind::Apply { args, .. } => {
167                for &arg in args.iter() {
168                    collect_vars_rec(arg, vars, visited, manager);
169                }
170            }
171            _ => {}
172        }
173    }
174
175    /// Substitute variables in a term
176    pub fn substitute(
177        term: TermId,
178        subst: &FxHashMap<Spur, TermId>,
179        manager: &mut TermManager,
180    ) -> TermId {
181        let mut cache = FxHashMap::default();
182        substitute_cached(term, subst, manager, &mut cache)
183    }
184
185    fn substitute_cached(
186        term: TermId,
187        subst: &FxHashMap<Spur, TermId>,
188        manager: &mut TermManager,
189        cache: &mut FxHashMap<TermId, TermId>,
190    ) -> TermId {
191        if let Some(&cached) = cache.get(&term) {
192            return cached;
193        }
194
195        let Some(t) = manager.get(term).cloned() else {
196            return term;
197        };
198
199        let result = match &t.kind {
200            TermKind::Var(name) => subst.get(name).copied().unwrap_or(term),
201            TermKind::Not(arg) => {
202                let new_arg = substitute_cached(*arg, subst, manager, cache);
203                manager.mk_not(new_arg)
204            }
205            TermKind::And(args) => {
206                let new_args: Vec<_> = args
207                    .iter()
208                    .map(|&a| substitute_cached(a, subst, manager, cache))
209                    .collect();
210                manager.mk_and(new_args)
211            }
212            TermKind::Or(args) => {
213                let new_args: Vec<_> = args
214                    .iter()
215                    .map(|&a| substitute_cached(a, subst, manager, cache))
216                    .collect();
217                manager.mk_or(new_args)
218            }
219            _ => term,
220        };
221
222        cache.insert(term, result);
223        result
224    }
225
226    /// Calculate term depth
227    pub fn term_depth(term: TermId, manager: &TermManager) -> usize {
228        let mut visited = FxHashMap::default();
229        term_depth_cached(term, manager, &mut visited)
230    }
231
232    fn term_depth_cached(
233        term: TermId,
234        manager: &TermManager,
235        visited: &mut FxHashMap<TermId, usize>,
236    ) -> usize {
237        if let Some(&cached) = visited.get(&term) {
238            return cached;
239        }
240
241        let Some(t) = manager.get(term) else {
242            return 1;
243        };
244
245        let depth = match &t.kind {
246            TermKind::And(args) | TermKind::Or(args) => {
247                let max_child = args
248                    .iter()
249                    .map(|&arg| term_depth_cached(arg, manager, visited))
250                    .max()
251                    .unwrap_or(0);
252                1 + max_child
253            }
254            TermKind::Not(arg) | TermKind::Neg(arg) => {
255                1 + term_depth_cached(*arg, manager, visited)
256            }
257            TermKind::Apply { args, .. } => {
258                let max_child = args
259                    .iter()
260                    .map(|&arg| term_depth_cached(arg, manager, visited))
261                    .max()
262                    .unwrap_or(0);
263                1 + max_child
264            }
265            _ => 1,
266        };
267
268        visited.insert(term, depth);
269        depth
270    }
271
272    /// Calculate term size (number of nodes)
273    pub fn term_size(term: TermId, manager: &TermManager) -> usize {
274        let mut visited = FxHashSet::default();
275        term_size_rec(term, manager, &mut visited)
276    }
277
278    fn term_size_rec(
279        term: TermId,
280        manager: &TermManager,
281        visited: &mut FxHashSet<TermId>,
282    ) -> usize {
283        if visited.contains(&term) {
284            return 0;
285        }
286        visited.insert(term);
287
288        let Some(t) = manager.get(term) else {
289            return 1;
290        };
291
292        let children_size = match &t.kind {
293            TermKind::And(args) | TermKind::Or(args) => args
294                .iter()
295                .map(|&arg| term_size_rec(arg, manager, visited))
296                .sum(),
297            TermKind::Not(arg) | TermKind::Neg(arg) => term_size_rec(*arg, manager, visited),
298            TermKind::Apply { args, .. } => args
299                .iter()
300                .map(|&arg| term_size_rec(arg, manager, visited))
301                .sum(),
302            _ => 0,
303        };
304
305        1 + children_size
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use oxiz_core::ast::TermManager;
313
314    #[test]
315    fn test_is_ground_constant() {
316        let manager = TermManager::new();
317        let term = manager.mk_true();
318        assert!(utils::is_ground(term, &manager));
319    }
320
321    #[test]
322    fn test_term_depth_constant() {
323        let manager = TermManager::new();
324        let term = manager.mk_true();
325        assert_eq!(utils::term_depth(term, &manager), 1);
326    }
327
328    #[test]
329    fn test_term_size_constant() {
330        let manager = TermManager::new();
331        let term = manager.mk_true();
332        assert_eq!(utils::term_size(term, &manager), 1);
333    }
334
335    #[test]
336    fn test_free_vars_constant() {
337        let manager = TermManager::new();
338        let term = manager.mk_true();
339        let vars = utils::free_vars(term, &manager);
340        assert_eq!(vars.len(), 0);
341    }
342}