1#[allow(unused_imports)]
7use crate::prelude::*;
8
9#[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_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_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_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_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_export]
68macro_rules! completion_error {
69 ($msg:expr) => {
70 $crate::mbqi::model_completion::CompletionError::CompletionFailed($msg.to_string())
71 };
72}
73
74#[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
88pub mod utils {
90 use crate::prelude::*;
91 use oxiz_core::ast::{TermId, TermKind, TermManager};
92 use oxiz_core::interner::Spur;
93
94 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 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 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 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 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}