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