1#[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_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_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_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_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_export]
61macro_rules! completion_error {
62 ($msg:expr) => {
63 $crate::mbqi::model_completion::CompletionError::CompletionFailed($msg.to_string())
64 };
65}
66
67#[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
81pub mod utils {
83 use lasso::Spur;
84 use oxiz_core::ast::{TermId, TermKind, TermManager};
85 use rustc_hash::{FxHashMap, FxHashSet};
86
87 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 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 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 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 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}