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