1#![allow(missing_docs)]
46#![allow(dead_code)]
47
48use lasso::Spur;
49use oxiz_core::ast::TermId;
50use oxiz_core::sort::SortId;
51use rustc_hash::FxHashMap;
52use smallvec::SmallVec;
53use std::fmt;
54
55pub mod counterexample;
56pub mod finite_model;
57pub mod heuristics;
58pub mod instantiation;
59pub mod integration;
60pub mod lazy_instantiation;
61pub mod macros;
62pub mod model_completion;
63pub mod patterns;
64
65pub use counterexample::{CounterExample, CounterExampleGenerator, RefinementStrategy};
67pub use finite_model::{FiniteModel, FiniteModelFinder, SymmetryBreaker, UniverseSize};
68pub use heuristics::{InstantiationHeuristic, MBQIHeuristics, SelectionStrategy, TriggerSelection};
69pub use instantiation::{
70 InstantiationContext, InstantiationEngine, InstantiationPattern, QuantifierInstantiator,
71};
72pub use integration::{MBQIIntegration, SolverCallback};
73pub use lazy_instantiation::{LazyInstantiator, LazyStrategy, MatchingContext};
74pub use model_completion::{
75 MacroSolver, ModelCompleter, ModelFixer, ProjectionFunction, UninterpretedSortHandler,
76};
77
78#[derive(Debug, Clone)]
80pub struct QuantifiedFormula {
81 pub term: TermId,
83 pub bound_vars: SmallVec<[(Spur, SortId); 4]>,
85 pub body: TermId,
87 pub is_universal: bool,
89 pub nesting_depth: u32,
91 pub instantiation_count: usize,
93 pub max_instantiations: usize,
95 pub generation: u32,
97 pub weight: f64,
99 pub patterns: Vec<Vec<TermId>>,
101 pub is_flattened: bool,
103 pub specialized_body: Option<TermId>,
105}
106
107impl QuantifiedFormula {
108 pub fn new(
110 term: TermId,
111 bound_vars: SmallVec<[(Spur, SortId); 4]>,
112 body: TermId,
113 is_universal: bool,
114 ) -> Self {
115 Self {
116 term,
117 bound_vars,
118 body,
119 is_universal,
120 nesting_depth: 0,
121 instantiation_count: 0,
122 max_instantiations: 1000,
123 generation: 0,
124 weight: 1.0,
125 patterns: Vec::new(),
126 is_flattened: false,
127 specialized_body: None,
128 }
129 }
130
131 pub fn with_params(
133 term: TermId,
134 bound_vars: SmallVec<[(Spur, SortId); 4]>,
135 body: TermId,
136 is_universal: bool,
137 max_instantiations: usize,
138 weight: f64,
139 ) -> Self {
140 let mut qf = Self::new(term, bound_vars, body, is_universal);
141 qf.max_instantiations = max_instantiations;
142 qf.weight = weight;
143 qf
144 }
145
146 pub fn can_instantiate(&self) -> bool {
148 self.instantiation_count < self.max_instantiations
149 }
150
151 pub fn num_vars(&self) -> usize {
153 self.bound_vars.len()
154 }
155
156 pub fn var_name(&self, idx: usize) -> Option<Spur> {
158 self.bound_vars.get(idx).map(|(name, _)| *name)
159 }
160
161 pub fn var_sort(&self, idx: usize) -> Option<SortId> {
163 self.bound_vars.get(idx).map(|(_, sort)| *sort)
164 }
165
166 pub fn record_instantiation(&mut self) {
168 self.instantiation_count += 1;
169 }
170
171 pub fn priority_score(&self) -> f64 {
173 let inst_factor = 1.0 / (1.0 + self.instantiation_count as f64);
178 let depth_factor = 1.0 / (1.0 + self.nesting_depth as f64);
179 self.weight * inst_factor * depth_factor
180 }
181}
182
183#[derive(Debug, Clone)]
185pub struct Instantiation {
186 pub quantifier: TermId,
188 pub substitution: FxHashMap<Spur, TermId>,
190 pub result: TermId,
192 pub generation: u32,
194 pub reason: InstantiationReason,
196 pub cost: f64,
198}
199
200impl Instantiation {
201 pub fn new(
203 quantifier: TermId,
204 substitution: FxHashMap<Spur, TermId>,
205 result: TermId,
206 generation: u32,
207 ) -> Self {
208 Self {
209 quantifier,
210 substitution,
211 result,
212 generation,
213 reason: InstantiationReason::ModelBased,
214 cost: 1.0,
215 }
216 }
217
218 pub fn with_reason(
220 quantifier: TermId,
221 substitution: FxHashMap<Spur, TermId>,
222 result: TermId,
223 generation: u32,
224 reason: InstantiationReason,
225 ) -> Self {
226 Self {
227 quantifier,
228 substitution,
229 result,
230 generation,
231 reason,
232 cost: 1.0,
233 }
234 }
235
236 pub fn binding_key(&self) -> Vec<(Spur, TermId)> {
238 let mut key: Vec<_> = self.substitution.iter().map(|(&k, &v)| (k, v)).collect();
239 key.sort_by_key(|(name, _)| *name);
240 key
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
246pub enum InstantiationReason {
247 ModelBased,
249 EMatching,
251 Conflict,
253 Enumerative,
255 User,
257 Theory,
259}
260
261impl fmt::Display for InstantiationReason {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 match self {
264 Self::ModelBased => write!(f, "model-based"),
265 Self::EMatching => write!(f, "e-matching"),
266 Self::Conflict => write!(f, "conflict"),
267 Self::Enumerative => write!(f, "enumerative"),
268 Self::User => write!(f, "user"),
269 Self::Theory => write!(f, "theory"),
270 }
271 }
272}
273
274#[derive(Debug, Clone)]
276pub enum MBQIResult {
277 NoQuantifiers,
279 Satisfied,
281 NewInstantiations(Vec<Instantiation>),
283 Conflict {
285 quantifier: TermId,
286 reason: Vec<TermId>,
287 },
288 InstantiationLimit,
290 Unknown,
292}
293
294impl MBQIResult {
295 pub fn is_sat(&self) -> bool {
297 matches!(self, Self::Satisfied)
298 }
299
300 pub fn is_unsat(&self) -> bool {
302 matches!(self, Self::Conflict { .. })
303 }
304
305 pub fn has_instantiations(&self) -> bool {
307 matches!(self, Self::NewInstantiations(_))
308 }
309
310 pub fn num_instantiations(&self) -> usize {
312 match self {
313 Self::NewInstantiations(insts) => insts.len(),
314 _ => 0,
315 }
316 }
317}
318
319#[derive(Debug, Clone, Default)]
321pub struct MBQIStats {
322 pub num_quantifiers: usize,
324 pub total_instantiations: usize,
326 pub max_instantiations: usize,
328 pub unique_instantiations: usize,
330 pub num_checks: usize,
332 pub num_completions: usize,
334 pub num_counterexamples: usize,
336 pub num_conflicts: usize,
338 pub total_time_us: u64,
340 pub completion_time_us: u64,
342 pub cex_search_time_us: u64,
344}
345
346impl MBQIStats {
347 pub fn new() -> Self {
349 Self::default()
350 }
351
352 pub fn reset(&mut self) {
354 *self = Self::default();
355 }
356
357 pub fn avg_instantiations_per_check(&self) -> f64 {
359 if self.num_checks == 0 {
360 0.0
361 } else {
362 self.total_instantiations as f64 / self.num_checks as f64
363 }
364 }
365
366 pub fn avg_time_per_check_us(&self) -> f64 {
368 if self.num_checks == 0 {
369 0.0
370 } else {
371 self.total_time_us as f64 / self.num_checks as f64
372 }
373 }
374}
375
376impl fmt::Display for MBQIStats {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 writeln!(f, "MBQI Statistics:")?;
379 writeln!(f, " Quantifiers: {}", self.num_quantifiers)?;
380 writeln!(f, " Total checks: {}", self.num_checks)?;
381 writeln!(f, " Total instantiations: {}", self.total_instantiations)?;
382 writeln!(f, " Unique instantiations: {}", self.unique_instantiations)?;
383 writeln!(
384 f,
385 " Avg inst/check: {:.2}",
386 self.avg_instantiations_per_check()
387 )?;
388 writeln!(f, " Counterexamples: {}", self.num_counterexamples)?;
389 writeln!(f, " Conflicts: {}", self.num_conflicts)?;
390 writeln!(
391 f,
392 " Total time: {:.2}ms",
393 self.total_time_us as f64 / 1000.0
394 )?;
395 writeln!(
396 f,
397 " Completion time: {:.2}ms",
398 self.completion_time_us as f64 / 1000.0
399 )?;
400 writeln!(
401 f,
402 " CEX search time: {:.2}ms",
403 self.cex_search_time_us as f64 / 1000.0
404 )
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411 use lasso::Key;
412
413 #[test]
414 fn test_quantified_formula_creation() {
415 let qf = QuantifiedFormula::new(TermId::new(1), SmallVec::new(), TermId::new(2), true);
416 assert!(qf.is_universal);
417 assert_eq!(qf.instantiation_count, 0);
418 assert!(qf.can_instantiate());
419 }
420
421 #[test]
422 fn test_quantified_formula_priority() {
423 let mut qf = QuantifiedFormula::with_params(
424 TermId::new(1),
425 SmallVec::new(),
426 TermId::new(2),
427 true,
428 100,
429 2.0,
430 );
431 let initial_priority = qf.priority_score();
432 qf.record_instantiation();
433 let after_priority = qf.priority_score();
434 assert!(after_priority < initial_priority);
436 }
437
438 #[test]
439 fn test_instantiation_binding_key() {
440 let mut subst = FxHashMap::default();
441 subst.insert(
442 Spur::try_from_usize(1).expect("valid spur"),
443 TermId::new(10),
444 );
445 subst.insert(
446 Spur::try_from_usize(2).expect("valid spur"),
447 TermId::new(20),
448 );
449
450 let inst = Instantiation::new(TermId::new(1), subst, TermId::new(3), 0);
451 let key = inst.binding_key();
452 assert_eq!(key.len(), 2);
453 assert!(key[0].0 <= key[1].0);
455 }
456
457 #[test]
458 fn test_mbqi_result_predicates() {
459 let sat = MBQIResult::Satisfied;
460 assert!(sat.is_sat());
461 assert!(!sat.is_unsat());
462 assert!(!sat.has_instantiations());
463
464 let conflict = MBQIResult::Conflict {
465 quantifier: TermId::new(1),
466 reason: vec![],
467 };
468 assert!(!conflict.is_sat());
469 assert!(conflict.is_unsat());
470
471 let inst = MBQIResult::NewInstantiations(vec![]);
472 assert!(inst.has_instantiations());
473 assert_eq!(inst.num_instantiations(), 0);
474 }
475
476 #[test]
477 fn test_stats_initialization() {
478 let stats = MBQIStats::new();
479 assert_eq!(stats.num_quantifiers, 0);
480 assert_eq!(stats.total_instantiations, 0);
481 assert_eq!(stats.avg_instantiations_per_check(), 0.0);
482 }
483
484 #[test]
485 fn test_stats_averages() {
486 let mut stats = MBQIStats::new();
487 stats.num_checks = 10;
488 stats.total_instantiations = 50;
489 stats.total_time_us = 1000;
490
491 assert_eq!(stats.avg_instantiations_per_check(), 5.0);
492 assert_eq!(stats.avg_time_per_check_us(), 100.0);
493 }
494
495 #[test]
496 fn test_instantiation_reason_display() {
497 assert_eq!(
498 format!("{}", InstantiationReason::ModelBased),
499 "model-based"
500 );
501 assert_eq!(format!("{}", InstantiationReason::EMatching), "e-matching");
502 assert_eq!(format!("{}", InstantiationReason::Conflict), "conflict");
503 }
504}