1#![allow(missing_docs)]
7#![allow(dead_code)]
8
9use lasso::Spur;
10use oxiz_core::ast::{TermId, TermManager};
11use oxiz_core::sort::SortId;
12use rustc_hash::FxHashMap;
13use smallvec::SmallVec;
14use std::fmt;
15use std::time::{Duration, Instant};
16
17use super::counterexample::CounterExampleGenerator;
18use super::finite_model::FiniteModelFinder;
19use super::instantiation::InstantiationEngine;
20use super::lazy_instantiation::LazyInstantiator;
21use super::model_completion::ModelCompleter;
22use super::{Instantiation, MBQIResult, MBQIStats, QuantifiedFormula};
23
24pub trait SolverCallback: fmt::Debug {
26 fn on_instantiation(&mut self, inst: &Instantiation);
28
29 fn on_conflict(&mut self, quantifier: TermId, reason: &[TermId]);
31
32 fn on_round_start(&mut self, round: usize);
34
35 fn on_round_end(&mut self, round: usize, result: &MBQIResult);
37
38 fn should_stop(&self) -> bool;
40}
41
42#[derive(Debug)]
44pub struct MBQIIntegration {
45 model_completer: ModelCompleter,
47 instantiation_engine: InstantiationEngine,
49 lazy_instantiator: LazyInstantiator,
51 finite_model_finder: FiniteModelFinder,
53 cex_generator: CounterExampleGenerator,
55 quantifiers: Vec<QuantifiedFormula>,
57 generated_instantiations: FxHashMap<InstantiationKey, usize>,
59 current_round: usize,
61 max_rounds: usize,
63 time_limit: Option<Duration>,
65 start_time: Option<Instant>,
67 stats: MBQIStats,
69}
70
71impl MBQIIntegration {
72 pub fn new() -> Self {
74 Self {
75 model_completer: ModelCompleter::new(),
76 instantiation_engine: InstantiationEngine::new(),
77 lazy_instantiator: LazyInstantiator::new(),
78 finite_model_finder: FiniteModelFinder::new(),
79 cex_generator: CounterExampleGenerator::new(),
80 quantifiers: Vec::new(),
81 generated_instantiations: FxHashMap::default(),
82 current_round: 0,
83 max_rounds: 100,
84 time_limit: Some(Duration::from_secs(60)),
85 start_time: None,
86 stats: MBQIStats::new(),
87 }
88 }
89
90 pub fn add_quantifier(&mut self, term: TermId, manager: &TermManager) {
92 let Some(t) = manager.get(term) else {
93 return;
94 };
95
96 match &t.kind {
97 oxiz_core::ast::TermKind::Forall { vars, body, .. } => {
98 let bound_vars: SmallVec<[(Spur, SortId); 4]> = vars.iter().copied().collect();
99 self.quantifiers
100 .push(QuantifiedFormula::new(term, bound_vars, *body, true));
101 self.stats.num_quantifiers += 1;
102 }
103 oxiz_core::ast::TermKind::Exists { vars, body, .. } => {
104 let bound_vars: SmallVec<[(Spur, SortId); 4]> = vars.iter().copied().collect();
105 self.quantifiers
106 .push(QuantifiedFormula::new(term, bound_vars, *body, false));
107 self.stats.num_quantifiers += 1;
108 }
109 _ => {}
110 }
111 }
112
113 pub fn run(
115 &mut self,
116 partial_model: &FxHashMap<TermId, TermId>,
117 manager: &mut TermManager,
118 callback: &mut dyn SolverCallback,
119 ) -> MBQIResult {
120 self.start_time = Some(Instant::now());
121 self.current_round = 0;
122
123 if self.quantifiers.is_empty() {
124 return MBQIResult::NoQuantifiers;
125 }
126
127 while self.current_round < self.max_rounds {
129 if self.check_timeout() || callback.should_stop() {
130 return MBQIResult::Unknown;
131 }
132
133 self.current_round += 1;
134 callback.on_round_start(self.current_round);
135 self.stats.num_checks += 1;
136
137 let round_start = Instant::now();
138
139 let completed_model =
141 match self
142 .model_completer
143 .complete(partial_model, &self.quantifiers, manager)
144 {
145 Ok(model) => {
146 self.stats.num_completions += 1;
147 model
148 }
149 Err(_) => {
150 callback.on_round_end(self.current_round, &MBQIResult::Unknown);
151 return MBQIResult::Unknown;
152 }
153 };
154
155 let mut all_instantiations = Vec::new();
157
158 let quantifiers: Vec<_> = self.quantifiers.to_vec();
160 for quantifier in quantifiers {
161 if !quantifier.can_instantiate() {
162 continue;
163 }
164
165 let instantiations =
166 self.instantiation_engine
167 .instantiate(&quantifier, &completed_model, manager);
168
169 for inst in instantiations {
170 if !self.is_duplicate(&inst) {
171 self.record_instantiation(&inst);
172 callback.on_instantiation(&inst);
173 all_instantiations.push(inst);
174 }
175 }
176 }
177
178 self.stats.completion_time_us += round_start.elapsed().as_micros() as u64;
179
180 if all_instantiations.is_empty() {
182 let result = MBQIResult::Satisfied;
183 callback.on_round_end(self.current_round, &result);
184 self.update_final_stats();
185 return result;
186 }
187
188 if self.stats.total_instantiations >= self.stats.max_instantiations {
190 let result = MBQIResult::InstantiationLimit;
191 callback.on_round_end(self.current_round, &result);
192 self.update_final_stats();
193 return result;
194 }
195
196 let result = MBQIResult::NewInstantiations(all_instantiations);
197 callback.on_round_end(self.current_round, &result);
198 }
199
200 self.update_final_stats();
201 MBQIResult::Unknown
202 }
203
204 fn is_duplicate(&self, inst: &Instantiation) -> bool {
206 let key = InstantiationKey::from(inst);
207 self.generated_instantiations.contains_key(&key)
208 }
209
210 fn record_instantiation(&mut self, inst: &Instantiation) {
212 let key = InstantiationKey::from(inst);
213 let count = self.generated_instantiations.entry(key).or_insert(0);
214 *count += 1;
215 self.stats.total_instantiations += 1;
216 self.stats.unique_instantiations = self.generated_instantiations.len();
217 }
218
219 fn check_timeout(&self) -> bool {
221 if let (Some(limit), Some(start)) = (self.time_limit, self.start_time) {
222 start.elapsed() >= limit
223 } else {
224 false
225 }
226 }
227
228 fn update_final_stats(&mut self) {
230 if let Some(start) = self.start_time {
231 self.stats.total_time_us = start.elapsed().as_micros() as u64;
232 }
233 }
234
235 pub fn clear(&mut self) {
237 self.quantifiers.clear();
238 self.generated_instantiations.clear();
239 self.current_round = 0;
240 self.start_time = None;
241 self.instantiation_engine.clear_caches();
242 self.lazy_instantiator.clear();
243 }
244
245 pub fn collect_ground_terms(&mut self, _term: TermId, _manager: &TermManager) {
247 }
250
251 pub fn check_with_model(
253 &mut self,
254 model: &FxHashMap<TermId, TermId>,
255 manager: &mut TermManager,
256 ) -> MBQIResult {
257 #[derive(Debug)]
259 struct NoOpCallback;
260 impl SolverCallback for NoOpCallback {
261 fn on_instantiation(&mut self, _: &Instantiation) {}
262 fn on_round_start(&mut self, _: usize) {}
263 fn on_round_end(&mut self, _: usize, _: &MBQIResult) {}
264 fn on_conflict(&mut self, _: TermId, _: &[TermId]) {}
265 fn should_stop(&self) -> bool {
266 false
267 }
268 }
269 let mut callback = NoOpCallback;
270 self.run(model, manager, &mut callback)
271 }
272
273 pub fn stats(&self) -> &MBQIStats {
275 &self.stats
276 }
277
278 pub fn set_max_rounds(&mut self, max: usize) {
280 self.max_rounds = max;
281 }
282
283 pub fn set_time_limit(&mut self, limit: Duration) {
285 self.time_limit = Some(limit);
286 }
287
288 pub fn add_candidate(&mut self, _term: TermId, _sort: SortId) {
290 }
293
294 pub fn is_enabled(&self) -> bool {
296 true
298 }
299}
300
301impl Default for MBQIIntegration {
302 fn default() -> Self {
303 Self::new()
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Hash)]
309struct InstantiationKey {
310 quantifier: TermId,
311 binding: Vec<(lasso::Spur, TermId)>,
312}
313
314impl From<&Instantiation> for InstantiationKey {
315 fn from(inst: &Instantiation) -> Self {
316 let mut binding: Vec<_> = inst.substitution.iter().map(|(&k, &v)| (k, v)).collect();
317 binding.sort_by_key(|(k, _)| *k);
318 Self {
319 quantifier: inst.quantifier,
320 binding,
321 }
322 }
323}
324
325#[derive(Debug)]
327pub struct DefaultCallback {
328 stop_requested: bool,
329}
330
331impl DefaultCallback {
332 pub fn new() -> Self {
333 Self {
334 stop_requested: false,
335 }
336 }
337
338 pub fn request_stop(&mut self) {
339 self.stop_requested = true;
340 }
341}
342
343impl Default for DefaultCallback {
344 fn default() -> Self {
345 Self::new()
346 }
347}
348
349impl SolverCallback for DefaultCallback {
350 fn on_instantiation(&mut self, _inst: &Instantiation) {}
351 fn on_conflict(&mut self, _quantifier: TermId, _reason: &[TermId]) {}
352 fn on_round_start(&mut self, _round: usize) {}
353 fn on_round_end(&mut self, _round: usize, _result: &MBQIResult) {}
354 fn should_stop(&self) -> bool {
355 self.stop_requested
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362
363 #[test]
364 fn test_mbqi_integration_creation() {
365 let integration = MBQIIntegration::new();
366 assert_eq!(integration.quantifiers.len(), 0);
367 assert_eq!(integration.current_round, 0);
368 }
369
370 #[test]
371 fn test_default_callback() {
372 let mut callback = DefaultCallback::new();
373 assert!(!callback.should_stop());
374 callback.request_stop();
375 assert!(callback.should_stop());
376 }
377
378 #[test]
379 fn test_integration_clear() {
380 let mut integration = MBQIIntegration::new();
381 integration.current_round = 5;
382 integration.clear();
383 assert_eq!(integration.current_round, 0);
384 assert_eq!(integration.quantifiers.len(), 0);
385 }
386
387 #[test]
388 fn test_set_max_rounds() {
389 let mut integration = MBQIIntegration::new();
390 integration.set_max_rounds(50);
391 assert_eq!(integration.max_rounds, 50);
392 }
393
394 #[test]
395 fn test_set_time_limit() {
396 let mut integration = MBQIIntegration::new();
397 let limit = Duration::from_secs(30);
398 integration.set_time_limit(limit);
399 assert_eq!(integration.time_limit, Some(limit));
400 }
401}